/*
 * IPP Everywhere printer application for CUPS.
 *
 * Copyright © 2021-2023 by OpenPrinting.
 * Copyright © 2020 by the IEEE-ISTO Printer Working Group.
 * Copyright © 2010-2021 by Apple Inc.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 *
 * Note: This program began life as the "ippserver" sample code that first
 * appeared in CUPS 1.4.  The name has been changed in order to distinguish it
 * from the PWG's much more ambitious "ippserver" program, which supports
 * different kinds of IPP services and multiple services per instance - the
 * "ippeveprinter" program exposes a single print service conforming to the
 * current IPP Everywhere specification, thus the new name.
 */

/*
 * Include necessary headers...
 */

/* #include <cups/cups-private.h> */
/* #include <cups/debug-private.h> */
/* #if !CUPS_LITE */
/* #  include <cups/ppd-private.h> */
/* #endif /\* !CUPS_LITE *\/ */
#include <cups/cups-private.h>
#include <cups/cups.h>
#include <cups/ppd-private.h>
#include <cups/ppd.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>

#ifdef _WIN32
#include <io.h>
#include <process.h>
#define WEXITSTATUS(s) (s)
#include <winsock2.h>
typedef ULONG nfds_t;
#define poll WSAPoll
#else
extern char **environ;

#include <poll.h>
#include <spawn.h>
#include <sys/wait.h>
#endif /* _WIN32 */

#ifndef O_BINARY
#define O_BINARY 0 /* Windows "binary file" nonsense */
#endif             /* !O_BINARY */

#ifdef HAVE_MDNSRESPONDER
#include <dns_sd.h>
#elif defined(HAVE_AVAHI)
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/thread-watch.h>
#endif /* HAVE_MDNSRESPONDER */

#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif /* HAVE_SYS_MOUNT_H */
#ifdef HAVE_SYS_STATFS_H
#include <sys/statfs.h>
#endif /* HAVE_SYS_STATFS_H */
#ifdef HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif /* HAVE_SYS_STATVFS_H */
#ifdef HAVE_SYS_VFS_H
#include <sys/vfs.h>
#endif /* HAVE_SYS_VFS_H */

#if HAVE_LIBPAM
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#else
#include <security/pam_appl.h>
#endif /* HAVE_PAM_PAM_APPL_H */
#endif /* HAVE_LIBPAM */

#include "printer-lg-png.h"
#include "printer-png.h"
#include "printer-sm-png.h"

/*
 * Constants...
 */

enum ippeve_preason_e /* printer-state-reasons bit values */
{
    IPPEVE_PREASON_NONE = 0x0000,       /* none */
    IPPEVE_PREASON_OTHER = 0x0001,      /* other */
    IPPEVE_PREASON_COVER_OPEN = 0x0002, /* cover-open */
    IPPEVE_PREASON_INPUT_TRAY_MISSING = 0x0004,
    /* input-tray-missing */
    IPPEVE_PREASON_MARKER_SUPPLY_EMPTY = 0x0008,
    /* marker-supply-empty */
    IPPEVE_PREASON_MARKER_SUPPLY_LOW = 0x0010,
    /* marker-supply-low */
    IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL = 0x0020,
    /* marker-waste-almost-full */
    IPPEVE_PREASON_MARKER_WASTE_FULL = 0x0040,
    /* marker-waste-full */
    IPPEVE_PREASON_MEDIA_EMPTY = 0x0080,  /* media-empty */
    IPPEVE_PREASON_MEDIA_JAM = 0x0100,    /* media-jam */
    IPPEVE_PREASON_MEDIA_LOW = 0x0200,    /* media-low */
    IPPEVE_PREASON_MEDIA_NEEDED = 0x0400, /* media-needed */
    IPPEVE_PREASON_MOVING_TO_PAUSED = 0x0800,
    /* moving-to-paused */
    IPPEVE_PREASON_PAUSED = 0x1000,          /* paused */
    IPPEVE_PREASON_SPOOL_AREA_FULL = 0x2000, /* spool-area-full */
    IPPEVE_PREASON_TONER_EMPTY = 0x4000,     /* toner-empty */
    IPPEVE_PREASON_TONER_LOW = 0x8000        /* toner-low */
};
typedef unsigned int     ippeve_preason_t;           /* Bitfield for printer-state-reasons */
static const char *const ippeve_preason_strings[] = {/* Strings for each bit */
    /* "none" is implied for no bits set */
    "other", "cover-open", "input-tray-missing", "marker-supply-empty", "marker-supply-low", "marker-waste-almost-full",
    "marker-waste-full", "media-empty", "media-jam", "media-low", "media-needed", "moving-to-paused", "paused",
    "spool-area-full", "toner-empty", "toner-low"};

/*
 * URL scheme for web resources...
 */

#ifdef HAVE_TLS
#define WEB_SCHEME "https"
#else
#define WEB_SCHEME "http"
#endif /* HAVE_TLS */

/*
 * Structures...
 */

#ifdef HAVE_MDNSRESPONDER
typedef DNSServiceRef ippeve_srv_t; /* Service reference */
typedef TXTRecordRef  ippeve_txt_t; /* TXT record */

#elif defined(HAVE_AVAHI)
typedef AvahiEntryGroup *ippeve_srv_t; /* Service reference */
typedef AvahiStringList *ippeve_txt_t; /* TXT record */

#else
typedef void *ippeve_srv_t; /* Service reference */
typedef void *ippeve_txt_t; /* TXT record */
#endif /* HAVE_MDNSRESPONDER */

#if HAVE_LIBPAM
typedef struct ippeve_authdata_s /* Authentication data */
{
        char username[HTTP_MAX_VALUE], /* Username string */
            *password;                 /* Password string */
} ippeve_authdata_t;
#endif /* HAVE_LIBPAM */

typedef struct ippeve_filter_s /**** Attribute filter ****/
{
        cups_array_t *ra;        /* Requested attributes */
        ipp_tag_t     group_tag; /* Group to copy */
} ippeve_filter_t;

typedef struct ippeve_job_s ippeve_job_t;

typedef struct ippeve_printer_s /**** Printer data ****/
{
        /* TODO: One IPv4 and one IPv6 listener are really not sufficient */
        int ipv4, /* IPv4 listener */
            ipv6; /* IPv6 listener */
#ifdef HAVE_MDNSRESPONDER
        ippeve_srv_t ipp_ref, /* DNS-SD IPP service */
            ipps_ref,         /* DNS-SD IPPS service */
            http_ref,         /* DNS-SD HTTP service */
            printer_ref;      /* DNS-SD LPD service */
#elif defined(HAVE_AVAHI)
        ippeve_srv_t dnssd_ref; /* DNS-SD services */
#endif                         /* HAVE_MDNSRESPONDER */
        char *dnssd_subtypes;  /* DNS-SD subtypes */
        int   dnssd_collision; /* Name collision? */
        char *dnssd_name,      /* printer-dns-sd-name */
            *name,             /* printer-name */
            *icons[3],         /* Icon filenames */
            *strings,          /* Strings filename */
            *directory,        /* Spool directory */
            *hostname,         /* Hostname */
            *device_uri,       /* Device URI (if any) */
            *output_format,    /* Output format */
#if !CUPS_LITE
            *ppdfile,                   /* PPD file (if any) */
#endif                                  /* !CUPS_LITE */
            *command;                   /* Command to run with job file */
        int              port;          /* Port */
        int              web_forms;     /* Enable web interface forms? */
        size_t           urilen;        /* Length of printer URI */
        ipp_t           *attrs;         /* Static attributes */
        time_t           start_time;    /* Startup time */
        time_t           config_time;   /* printer-config-change-time */
        ipp_pstate_t     state;         /* printer-state value */
        ippeve_preason_t state_reasons; /* printer-state-reasons values */
        time_t           state_time;    /* printer-state-change-time */
        cups_array_t    *jobs;          /* Jobs */
        ippeve_job_t    *active_job;    /* Current active/pending job */
        int              next_job_id;   /* Next job-id value */
        _cups_rwlock_t   rwlock;        /* Printer lock */
} ippeve_printer_t;

struct ippeve_job_s /**** Job data ****/
{
        int         id;             /* Job ID */
        const char *name,           /* job-name */
            *username,              /* job-originating-user-name */
            *format;                /* document-format */
        ipp_jstate_t state;         /* job-state value */
        char        *message;       /* job-state-message value */
        int          msglevel;      /* job-state-message log level (0=error, 1=info) */
        time_t       created,       /* time-at-creation value */
            processing,             /* time-at-processing value */
            completed;              /* time-at-completed value */
        int impressions,            /* job-impressions value */
            impcompleted;           /* job-impressions-completed value */
        ipp_t            *attrs;    /* Static attributes */
        int               cancel;   /* Non-zero when job canceled */
        char             *filename; /* Print file name */
        int               fd;       /* Print file descriptor */
        ippeve_printer_t *printer;  /* Printer */
};

typedef struct ippeve_client_s /**** Client data ****/
{
        http_t *http;              /* HTTP connection */
        ipp_t  *request,           /* IPP request */
            *response;             /* IPP response */
        time_t       start;        /* Request start time */
        http_state_t operation;    /* Request operation */
        ipp_op_t     operation_id; /* IPP operation-id */
        char         uri[1024],    /* Request URI */
            *options,              /* URI options */
            host_field[HTTP_MAX_VALUE];
        /* Host: header */
        int         host_port;     /* Port number from Host: header */
        http_addr_t addr;          /* Client address */
        char        hostname[256], /* Client hostname */
            username[HTTP_MAX_VALUE];
        /* Authenticated username, if any */
        ippeve_printer_t *printer; /* Printer */
        ippeve_job_t     *job;     /* Current job, if any */
} ippeve_client_t;

/*
 * Local functions...
 */

static http_status_t    authenticate_request (ippeve_client_t *client);
static void             clean_jobs (ippeve_printer_t *printer);
static int              compare_jobs (ippeve_job_t *a, ippeve_job_t *b);
static void             copy_attributes (ipp_t *to, ipp_t *from, cups_array_t *ra, ipp_tag_t group_tag, int quickcopy);
static void             copy_job_attributes (ippeve_client_t *client, ippeve_job_t *job, cups_array_t *ra);
static ippeve_client_t *create_client (ippeve_printer_t *printer, int sock);
static ippeve_job_t    *create_job (ippeve_client_t *client);
static int    create_job_file (ippeve_job_t *job, char *fname, size_t fnamesize, const char *dir, const char *ext);
static int    create_listener (const char *name, int port, int family);
static ipp_t *create_media_col (const char *media, const char *source, const char *type, int width, int length,
    int bottom, int left, int right, int top);
static ipp_t *create_media_size (int width, int length);
static ippeve_printer_t *create_printer (const char *servername, int serverport, const char *name, const char *location,
    const char *icons, const char *strings, cups_array_t *docformats, const char *subtypes, const char *directory,
    const char *command, const char *device_uri, const char *output_format, ipp_t *attrs);
static void              debug_attributes (const char *title, ipp_t *ipp, int response);
static void              delete_client (ippeve_client_t *client);
static void              delete_job (ippeve_job_t *job);
static void              delete_printer (ippeve_printer_t *printer);
#ifdef HAVE_MDNSRESPONDER
static void DNSSD_API dnssd_callback (DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
    const char *name, const char *regtype, const char *domain, ippeve_printer_t *printer);
#elif defined(HAVE_AVAHI)
static void dnssd_callback (AvahiEntryGroup *p, AvahiEntryGroupState state, void *context);
static void dnssd_client_cb (AvahiClient *c, AvahiClientState state, void *userdata);
#endif /* HAVE_MDNSRESPONDER */
static void          dnssd_init (void);
static int           filter_cb (ippeve_filter_t *filter, ipp_t *dst, ipp_attribute_t *attr);
static ippeve_job_t *find_job (ippeve_client_t *client);
static void          finish_document_data (ippeve_client_t *client, ippeve_job_t *job);
static void          finish_document_uri (ippeve_client_t *client, ippeve_job_t *job);
static void          flush_document_data (ippeve_client_t *client);
static int           have_document_data (ippeve_client_t *client);
static void          html_escape (ippeve_client_t *client, const char *s, size_t slen);
static void          html_footer (ippeve_client_t *client);
static void          html_header (ippeve_client_t *client, const char *title, int refresh);
static void          html_printf (ippeve_client_t *client, const char *format, ...) _CUPS_FORMAT (2, 3);
static void          ipp_cancel_job (ippeve_client_t *client);
static void          ipp_cancel_my_jobs (ippeve_client_t *client);
static void          ipp_close_job (ippeve_client_t *client);
static void          ipp_create_job (ippeve_client_t *client);
static void          ipp_get_job_attributes (ippeve_client_t *client);
static void          ipp_get_jobs (ippeve_client_t *client);
static void          ipp_get_printer_attributes (ippeve_client_t *client);
static void          ipp_identify_printer (ippeve_client_t *client);
static void          ipp_print_job (ippeve_client_t *client);
static void          ipp_print_uri (ippeve_client_t *client);
static void          ipp_send_document (ippeve_client_t *client);
static void          ipp_send_uri (ippeve_client_t *client);
static void          ipp_validate_job (ippeve_client_t *client);
static ipp_t        *load_ippserver_attributes (
           const char *servername, int serverport, const char *filename, cups_array_t *docformats);
static ipp_t *load_legacy_attributes (
    const char *make, const char *model, int ppm, int ppm_color, int duplex, cups_array_t *docformats);
#if !CUPS_LITE
static ipp_t *load_ppd_attributes (const char *ppdfile, cups_array_t *docformats);
#endif /* !CUPS_LITE */
#if HAVE_LIBPAM
static int pam_func (int, const struct pam_message **, struct pam_response **, void *);
#endif /* HAVE_LIBPAM */
static int   parse_options (ippeve_client_t *client, cups_option_t **options);
static void  process_attr_message (ippeve_job_t *job, char *message);
static void *process_client (ippeve_client_t *client);
static int   process_http (ippeve_client_t *client);
static int   process_ipp (ippeve_client_t *client);
static void *process_job (ippeve_job_t *job);
static void  process_state_message (ippeve_job_t *job, char *message);
static int   register_printer (ippeve_printer_t *printer);
static int   respond_http (
      ippeve_client_t *client, http_status_t code, const char *content_coding, const char *type, size_t length);
static void respond_ignored (ippeve_client_t *client, ipp_attribute_t *attr);
static void respond_ipp (ippeve_client_t *client, ipp_status_t status, const char *message, ...) _CUPS_FORMAT (3, 4);
static void respond_unsupported (ippeve_client_t *client, ipp_attribute_t *attr);
static void run_printer (ippeve_printer_t *printer);
static int  show_media (ippeve_client_t *client);
static int  show_status (ippeve_client_t *client);
static int  show_supplies (ippeve_client_t *client);
#ifndef _WIN32
static void signal_handler (int signum);
#endif // !_WIN32
static char *time_string (time_t tv, char *buffer, size_t bufsize);
static void  usage (int status) _CUPS_NORETURN;
static int   valid_doc_attributes (ippeve_client_t *client);
static int   valid_job_attributes (ippeve_client_t *client);

/*
 * Globals...
 */

#ifdef HAVE_MDNSRESPONDER
static DNSServiceRef DNSSDMaster = NULL;
#elif defined(HAVE_AVAHI)
static AvahiThreadedPoll *DNSSDMaster = NULL;
static AvahiClient       *DNSSDClient = NULL;
#endif /* HAVE_MDNSRESPONDER */

static int KeepFiles = 0, /* Keep spooled job files? */
    MaxVersion = 20,      /* Maximum IPP version (20 = 2.0, 11 = 1.1, etc.) */
    Verbosity = 0;        /* Verbosity level */
static const char *PAMService = NULL;
/* PAM service */
#ifndef _WIN32
static int StopPrinter = 0; /* Stop the printer server? */
#endif                      // !_WIN32

/*
 * 'main()' - Main entry to the sample server.
 */

int               /* O - Exit status */
main (int argc,   /* I - Number of command-line args */
    char *argv[]) /* I - Command-line arguments */
{
    int         i;                         /* Looping var */
    const char *opt,                       /* Current option character */
        *attrfile = NULL,                  /* ippserver attributes file */
        *command = NULL,               /* Command to run with job files */
        *device_uri = NULL,        /* Device URI */
        *output_format = NULL, /* Output format */
        *icon = NULL,      /* Icon file */
#ifdef HAVE_TLS
        *keypath = NULL,                /* Keychain path */
#endif                                                      /* HAVE_TLS */
        *location = "",             /* Location of printer */
        *make = "Example",      /* Manufacturer */
        *model = "Printer", /* Model */
        *name = NULL,   /* Printer name */
#if !CUPS_LITE
        *ppdfile = NULL,              /* PPD file */
#endif                                                                        /* !CUPS_LITE */
        *strings = NULL,          /* Strings file */
        *subtypes = "_print"; /* DNS-SD service subtype */
    int legacy = 0,                                                           /* Legacy mode? */
        duplex = 0,                                                           /* Duplex mode */
        ppm = 10,                                                             /* Pages per minute for mono */
        ppm_color = 0,                                                        /* Pages per minute for color */
        web_forms = 1;                                                        /* Enable website forms? */
    ipp_t            *attrs = NULL;                                           /* Printer attributes */
    char              directory[1024] = "";                                   /* Spool directory */
    cups_array_t     *docformats = NULL;                                      /* Supported formats */
    const char       *servername = NULL;                                      /* Server host name */
    int               serverport = 0;                                         /* Server port number (0 = auto) */
    ippeve_printer_t *printer;                                                /* Printer object */

    /*
     * Parse command-line arguments...
     */

    for (i = 1; i < argc; i++) {
        if (!strcmp (argv[i], "--help")) {
            usage (0);
        } else if (!strcmp (argv[i], "--no-web-forms")) {
            web_forms = 0;
        } else if (!strcmp (argv[i], "--pam-service")) {
            i++;
            if (i >= argc)
                usage (1);

            PAMService = argv[i];
        } else if (!strcmp (argv[i], "--version")) {
            puts (CUPS_SVERSION);
            return (0);
        } else if (!strncmp (argv[i], "--", 2)) {
            _cupsLangPrintf (stderr, _ ("%s: Unknown option \"%s\"."), argv[0], argv[i]);
            usage (1);
        } else if (argv[i][0] == '-') {
            for (opt = argv[i] + 1; *opt; opt++) {
                switch (*opt) {
                case '2': /* -2 (enable 2-sided printing) */
                    duplex = 1;
                    legacy = 1;
                    break;

                case 'A': /* -A (enable authentication) */
                    if (!PAMService)
                        PAMService = "cups";
                    break;

                case 'D': /* -D device-uri */
                    i++;
                    if (i >= argc)
                        usage (1);

                    device_uri = argv[i];
                    break;

                case 'F': /* -F output/format */
                    i++;
                    if (i >= argc)
                        usage (1);

                    output_format = argv[i];
                    break;

#ifdef HAVE_TLS
                case 'K': /* -K keypath */
                    i++;
                    if (i >= argc)
                        usage (1);

                    keypath = argv[i];
                    break;
#endif /* HAVE_TLS */

                case 'M': /* -M manufacturer */
                    i++;
                    if (i >= argc)
                        usage (1);

                    make = argv[i];
                    legacy = 1;
                    break;

#if !CUPS_LITE
                case 'P': /* -P filename.ppd */
                    i++;
                    if (i >= argc)
                        usage (1);

                    ppdfile = argv[i];
                    break;
#endif /* !CUPS_LITE */

                case 'S': /* -S filename.strings */
                    i++;
                    if (i >= argc)
                        usage (1);

                    strings = argv[i];
                    break;

                case 'V': /* -V max-version */
                    i++;
                    if (i >= argc)
                        usage (1);

                    if (!strcmp (argv[i], "2.0"))
                        MaxVersion = 20;
                    else if (!strcmp (argv[i], "1.1"))
                        MaxVersion = 11;
                    else
                        usage (1);
                    break;

                case 'a': /* -a attributes-file */
                    i++;
                    if (i >= argc)
                        usage (1);

                    attrfile = argv[i];
                    break;

                case 'c': /* -c command */
                    i++;
                    if (i >= argc)
                        usage (1);

                    command = argv[i];
                    break;

                case 'd': /* -d spool-directory */
                    i++;
                    if (i >= argc)
                        usage (1);

                    strlcpy (directory, argv[i], sizeof (directory));
                    break;

                case 'f': /* -f type/subtype[,...] */
                    i++;
                    if (i >= argc)
                        usage (1);

                    docformats = _cupsArrayNewStrings (argv[i], ',');
                    legacy = 1;
                    break;

                case 'i': /* -i icon.png */
                    i++;
                    if (i >= argc)
                        usage (1);

                    icon = argv[i];
                    break;

                case 'k': /* -k (keep files) */
                    KeepFiles = 1;
                    break;

                case 'l': /* -l location */
                    i++;
                    if (i >= argc)
                        usage (1);

                    location = argv[i];
                    break;

                case 'm': /* -m model */
                    i++;
                    if (i >= argc)
                        usage (1);

                    model = argv[i];
                    legacy = 1;
                    break;

                case 'n': /* -n hostname */
                    i++;
                    if (i >= argc)
                        usage (1);

                    servername = argv[i];
                    break;

                case 'p': /* -p port */
                    i++;
                    if (i >= argc || !isdigit (argv[i][0] & 255))
                        usage (1);

                    serverport = atoi (argv[i]);
                    break;

                case 'r': /* -r subtype */
                    i++;
                    if (i >= argc)
                        usage (1);

                    subtypes = argv[i];
                    break;

                case 's': /* -s speed[,color-speed] */
                    i++;
                    if (i >= argc)
                        usage (1);

                    if (sscanf (argv[i], "%d,%d", &ppm, &ppm_color) < 1)
                        usage (1);

                    legacy = 1;
                    break;

                case 'v': /* -v (be verbose) */
                    Verbosity++;
                    break;

                default: /* Unknown */
                    _cupsLangPrintf (stderr, _ ("%s: Unknown option \"-%c\"."), argv[0], *opt);
                    usage (1);
                }
            }
        } else if (!name) {
            name = argv[i];
        } else {
            _cupsLangPrintf (stderr, _ ("%s: Unknown option \"%s\"."), argv[0], argv[i]);
            usage (1);
        }
    }

    if (!name)
        usage (1);

#if CUPS_LITE
    if (attrfile != NULL && legacy)
        usage (1);
#else
    if (((ppdfile != NULL) + (attrfile != NULL) + legacy) > 1)
        usage (1);
#endif /* CUPS_LITE */

    /*
     * Apply defaults as needed...
     */

    if (!directory[0]) {
        const char *tmpdir; /* Temporary directory */

#ifdef _WIN32
        if ((tmpdir = getenv ("TEMP")) == NULL)
            tmpdir = "C:/TEMP";
#elif defined(__APPLE__) && TARGET_OS_OSX
        if ((tmpdir = getenv ("TMPDIR")) == NULL)
            tmpdir = "/private/tmp";
#else
        if ((tmpdir = getenv ("TMPDIR")) == NULL)
            tmpdir = "/tmp";
#endif /* _WIN32 */

        snprintf (directory, sizeof (directory), "%s/ippeveprinter.%d", tmpdir, (int)getpid ());

        if (mkdir (directory, 0755) && errno != EEXIST) {
            _cupsLangPrintf (stderr, _ ("Unable to create spool directory \"%s\": %s"), directory, strerror (errno));
            usage (1);
        }

        if (Verbosity)
            _cupsLangPrintf (stderr, _ ("Using spool directory \"%s\"."), directory);
    }

    /*
     * Initialize DNS-SD...
     */

    dnssd_init ();

    /*
     * Create the printer...
     */

    if (!docformats)
        docformats = _cupsArrayNewStrings (
            ppm_color > 0 ? "image/jpeg,image/pwg-raster,image/urf" : "image/pwg-raster,image/urf", ',');

    if (attrfile)
        attrs = load_ippserver_attributes (servername, serverport, attrfile, docformats);
#if !CUPS_LITE
    else if (ppdfile) {
        attrs = load_ppd_attributes (ppdfile, docformats);

        if (!command)
            command = "ippeveps";

        if (!output_format)
            output_format = "application/postscript";
    }
#endif /* !CUPS_LITE */
    else
        attrs = load_legacy_attributes (make, model, ppm, ppm_color, duplex, docformats);

    if ((printer = create_printer (servername, serverport, name, location, icon, strings, docformats, subtypes,
             directory, command, device_uri, output_format, attrs))
        == NULL)
        return (1);

    printer->web_forms = web_forms;

#if !CUPS_LITE
    if (ppdfile)
        printer->ppdfile = strdup (ppdfile);
#endif /* !CUPS_LITE */

#ifdef HAVE_TLS
    cupsSetServerCredentials (keypath, printer->hostname, 1);
#endif /* HAVE_TLS */

    /*
     * Run the print service...
     */

    run_printer (printer);

    /*
     * Destroy the printer and exit...
     */

    delete_printer (printer);

    return (0);
}

/*
 * 'authenticate_request()' - Try to authenticate the request.
 */

static http_status_t                           /* O - HTTP_STATUS_CONTINUE to keep going, otherwise status to return */
authenticate_request (ippeve_client_t *client) /* I - Client */
{
#if HAVE_LIBPAM
    /*
     * If PAM isn't enabled, return 'continue' now...
     */

    const char       *authorization; /* Pointer into Authorization string */
    int               userlen;       /* Username:password length */
    pam_handle_t     *pamh;          /* PAM authentication handle */
    int               pamerr;        /* PAM error code */
    struct pam_conv   pamdata;       /* PAM conversation data */
    ippeve_authdata_t data;          /* Authentication data */

    if (!PAMService)
        return (HTTP_STATUS_CONTINUE);

    /*
     * Try authenticating using PAM...
     */

    authorization = httpGetField (client->http, HTTP_FIELD_AUTHORIZATION);

    if (!*authorization)
        return (HTTP_STATUS_UNAUTHORIZED);

    if (strncmp (authorization, "Basic ", 6)) {
        fputs ("Unsupported scheme in Authorization header.\n", stderr);
        return (HTTP_STATUS_BAD_REQUEST);
    }

    authorization += 5;
    while (isspace (*authorization & 255))
        authorization++;

    userlen = sizeof (data.username);
    httpDecode64_2 (data.username, &userlen, authorization);

    if ((data.password = strchr (data.username, ':')) == NULL) {
        fputs ("No password in Authorization header.\n", stderr);
        return (HTTP_STATUS_BAD_REQUEST);
    }

    *(data.password)++ = '\0';

    if (!data.username[0]) {
        fputs ("No username in Authorization header.\n", stderr);
        return (HTTP_STATUS_BAD_REQUEST);
    }

    pamdata.conv = pam_func;
    pamdata.appdata_ptr = &data;

    if ((pamerr = pam_start (PAMService, data.username, &pamdata, &pamh)) != PAM_SUCCESS) {
        fprintf (stderr, "pam_start() returned %d (%s)\n", pamerr, pam_strerror (pamh, pamerr));
        return (HTTP_STATUS_SERVER_ERROR);
    }

    if ((pamerr = pam_authenticate (pamh, PAM_SILENT)) != PAM_SUCCESS) {
        fprintf (stderr, "pam_authenticate() returned %d (%s)\n", pamerr, pam_strerror (pamh, pamerr));
        pam_end (pamh, 0);
        return (HTTP_STATUS_UNAUTHORIZED);
    }

    if ((pamerr = pam_acct_mgmt (pamh, PAM_SILENT)) != PAM_SUCCESS) {
        fprintf (stderr, "pam_acct_mgmt() returned %d (%s)\n", pamerr, pam_strerror (pamh, pamerr));
        pam_end (pamh, 0);
        return (HTTP_STATUS_SERVER_ERROR);
    }

    strlcpy (client->username, data.username, sizeof (client->username));

    pam_end (pamh, PAM_SUCCESS);

    return (HTTP_STATUS_CONTINUE);

#else
    /*
     * No authentication support built-in, return 'continue'...
     */

    return (HTTP_STATUS_CONTINUE);
#endif /* HAVE_LIBPAM */
}

/*
 * 'clean_jobs()' - Clean out old (completed) jobs.
 */

static void clean_jobs (ippeve_printer_t *printer) /* I - Printer */
{
    ippeve_job_t *job;       /* Current job */
    time_t        cleantime; /* Clean time */

    if (cupsArrayCount (printer->jobs) == 0)
        return;

    cleantime = time (NULL) - 60;

    _cupsRWLockWrite (&(printer->rwlock));
    for (job = (ippeve_job_t *)cupsArrayFirst (printer->jobs); job; job = (ippeve_job_t *)cupsArrayNext (printer->jobs))
        if (job->completed && job->completed < cleantime) {
            cupsArrayRemove (printer->jobs, job);
            delete_job (job);
        } else
            break;
    _cupsRWUnlock (&(printer->rwlock));
}

/*
 * 'compare_jobs()' - Compare two jobs.
 */

static int                     /* O - Result of comparison */
compare_jobs (ippeve_job_t *a, /* I - First job */
    ippeve_job_t           *b)           /* I - Second job */
{
    return (b->id - a->id);
}

/*
 * 'copy_attributes()' - Copy attributes from one request to another.
 */

static void copy_attributes (ipp_t *to,        /* I - Destination request */
    ipp_t                          *from,      /* I - Source request */
    cups_array_t                   *ra,        /* I - Requested attributes */
    ipp_tag_t                       group_tag, /* I - Group to copy */
    int                             quickcopy)                             /* I - Do a quick copy? */
{
    ippeve_filter_t filter; /* Filter data */

    filter.ra = ra;
    filter.group_tag = group_tag;

    ippCopyAttributes (to, from, quickcopy, (ipp_copycb_t)filter_cb, &filter);
}

/*
 * 'copy_job_attrs()' - Copy job attributes to the response.
 */

static void copy_job_attributes (ippeve_client_t *client, /* I - Client */
    ippeve_job_t                                 *job,    /* I - Job */
    cups_array_t                                 *ra)                                     /* I - requested-attributes */
{
    copy_attributes (client->response, job->attrs, ra, IPP_TAG_JOB, 0);

    if (!ra || cupsArrayFind (ra, "date-time-at-completed")) {
        if (job->completed)
            ippAddDate (client->response, IPP_TAG_JOB, "date-time-at-completed", ippTimeToDate (job->completed));
        else
            ippAddOutOfBand (client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-completed");
    }

    if (!ra || cupsArrayFind (ra, "date-time-at-processing")) {
        if (job->processing)
            ippAddDate (client->response, IPP_TAG_JOB, "date-time-at-processing", ippTimeToDate (job->processing));
        else
            ippAddOutOfBand (client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-processing");
    }

    if (!ra || cupsArrayFind (ra, "job-impressions"))
        ippAddInteger (client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions", job->impressions);

    if (!ra || cupsArrayFind (ra, "job-impressions-completed"))
        ippAddInteger (client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", job->impcompleted);

    if (!ra || cupsArrayFind (ra, "job-printer-up-time"))
        ippAddInteger (client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-printer-up-time",
            (int)(time (NULL) - client->printer->start_time));

    if (!ra || cupsArrayFind (ra, "job-state"))
        ippAddInteger (client->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", (int)job->state);

    if (!ra || cupsArrayFind (ra, "job-state-message")) {
        if (job->message) {
            ippAddString (client->response, IPP_TAG_JOB, IPP_TAG_TEXT, "job-state-message", NULL, job->message);
        } else {
            switch (job->state) {
            case IPP_JSTATE_PENDING:
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message", NULL,
                    "Job pending.");
                break;

            case IPP_JSTATE_HELD:
                if (job->fd >= 0)
                    ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message",
                        NULL, "Job incoming.");
                else if (ippFindAttribute (job->attrs, "job-hold-until", IPP_TAG_ZERO))
                    ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message",
                        NULL, "Job held.");
                else
                    ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message",
                        NULL, "Job created.");
                break;

            case IPP_JSTATE_PROCESSING:
                if (job->cancel)
                    ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message",
                        NULL, "Job canceling.");
                else
                    ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message",
                        NULL, "Job printing.");
                break;

            case IPP_JSTATE_STOPPED:
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message", NULL,
                    "Job stopped.");
                break;

            case IPP_JSTATE_CANCELED:
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message", NULL,
                    "Job canceled.");
                break;

            case IPP_JSTATE_ABORTED:
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message", NULL,
                    "Job aborted.");
                break;

            case IPP_JSTATE_COMPLETED:
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_TEXT), "job-state-message", NULL,
                    "Job completed.");
                break;
            }
        }
    }

    if (!ra || cupsArrayFind (ra, "job-state-reasons")) {
        switch (job->state) {
        case IPP_JSTATE_PENDING:
            ippAddString (
                client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL, "none");
            break;

        case IPP_JSTATE_HELD:
            if (job->fd >= 0)
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                    "job-incoming");
            else if (ippFindAttribute (job->attrs, "job-hold-until", IPP_TAG_ZERO))
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                    "job-hold-until-specified");
            else
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                    "job-data-insufficient");
            break;

        case IPP_JSTATE_PROCESSING:
            if (job->cancel)
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                    "processing-to-stop-point");
            else
                ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                    "job-printing");
            break;

        case IPP_JSTATE_STOPPED:
            ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                "job-stopped");
            break;

        case IPP_JSTATE_CANCELED:
            ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                "job-canceled-by-user");
            break;

        case IPP_JSTATE_ABORTED:
            /* ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL, */
            /*     "aborted-by-system"); */
            fprintf(stderr,"IPP_JSTATE_ABORTED!!!\n");
            ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                "document-format-error");

            break;

        case IPP_JSTATE_COMPLETED:
            ippAddString (client->response, IPP_TAG_JOB, IPP_CONST_TAG (IPP_TAG_KEYWORD), "job-state-reasons", NULL,
                "job-completed-successfully");
            break;
        }
    }

    if (!ra || cupsArrayFind (ra, "time-at-completed"))
        ippAddInteger (client->response, IPP_TAG_JOB, job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
            "time-at-completed", (int)(job->completed - client->printer->start_time));

    if (!ra || cupsArrayFind (ra, "time-at-processing"))
        ippAddInteger (client->response, IPP_TAG_JOB, job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
            "time-at-processing", (int)(job->processing - client->printer->start_time));
}

/*
 * 'create_client()' - Accept a new network connection and create a client
 *                     object.
 */

static ippeve_client_t *                  /* O - Client */
create_client (ippeve_printer_t *printer, /* I - Printer */
    int                          sock)                             /* I - Listen socket */
{
    ippeve_client_t *client; /* Client */

    if ((client = calloc (1, sizeof (ippeve_client_t))) == NULL) {
        perror ("Unable to allocate memory for client");
        return (NULL);
    }

    client->printer = printer;

    /*
     * Accept the client and get the remote address...
     */

    if ((client->http = httpAcceptConnection (sock, 1)) == NULL) {
        perror ("Unable to accept client connection");

        free (client);

        return (NULL);
    }

    httpGetHostname (client->http, client->hostname, sizeof (client->hostname));

    if (Verbosity)
        fprintf (stderr, "Accepted connection from %s\n", client->hostname);

    return (client);
}

/*
 * 'create_job()' - Create a new job object from a Print-Job or Create-Job
 *                  request.
 */

static ippeve_job_t *                /* O - Job */
create_job (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t    *job;       /* Job */
    ipp_attribute_t *attr;      /* Job attribute */
    char             uri[1024], /* job-uri value */
        uuid[64];               /* job-uuid value */

    _cupsRWLockWrite (&(client->printer->rwlock));
    if (client->printer->active_job && client->printer->active_job->state < IPP_JSTATE_CANCELED) {
        /*
         * Only accept a single job at a time...
         */

        _cupsRWUnlock (&(client->printer->rwlock));
        return (NULL);
    }

    /*
     * Allocate and initialize the job object...
     */

    if ((job = calloc (1, sizeof (ippeve_job_t))) == NULL) {
        perror ("Unable to allocate memory for job");
        _cupsRWUnlock (&(client->printer->rwlock));
        return (NULL);
    }

    job->printer = client->printer;
    job->attrs = ippNew ();
    job->state = IPP_JSTATE_HELD;
    job->fd = -1;

    /*
     * Copy all of the job attributes...
     */

    copy_attributes (job->attrs, client->request, NULL, IPP_TAG_JOB, 0);

    /*
     * Get the requesting-user-name, document format, and priority...
     */

    if ((attr = ippFindAttribute (client->request, "requesting-user-name", IPP_TAG_NAME)) != NULL)
        job->username = ippGetString (attr, 0, NULL);
    else
        job->username = "anonymous";

    ippAddString (job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-originating-user-name", NULL, job->username);

    if (ippGetOperation (client->request) != IPP_OP_CREATE_JOB) {
        if ((attr = ippFindAttribute (job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL)
            job->format = ippGetString (attr, 0, NULL);
        else if ((attr = ippFindAttribute (job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL)
            job->format = ippGetString (attr, 0, NULL);
        else
            job->format = "application/octet-stream";
    }

    if ((attr = ippFindAttribute (client->request, "job-impressions", IPP_TAG_INTEGER)) != NULL)
        job->impressions = ippGetInteger (attr, 0);

    if ((attr = ippFindAttribute (client->request, "job-name", IPP_TAG_NAME)) != NULL)
        job->name = ippGetString (attr, 0, NULL);

    /*
     * Add job description attributes and add to the jobs array...
     */

    job->id = client->printer->next_job_id++;

    if ((attr = ippFindAttribute (client->request, "printer-uri", IPP_TAG_URI)) != NULL)
        snprintf (uri, sizeof (uri), "%s/%d", ippGetString (attr, 0, NULL), job->id);
    else
        httpAssembleURIf (HTTP_URI_CODING_ALL, uri, sizeof (uri), "ipp", NULL, client->printer->hostname,
            client->printer->port, "/ipp/print/%d", job->id);

    httpAssembleUUID (
        client->printer->hostname, client->printer->port, client->printer->name, job->id, uuid, sizeof (uuid));

    ippAddDate (job->attrs, IPP_TAG_JOB, "date-time-at-creation", ippTimeToDate (time (&job->created)));
    ippAddInteger (job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
    ippAddString (job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri);
    ippAddString (job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uuid", NULL, uuid);
    if ((attr = ippFindAttribute (client->request, "printer-uri", IPP_TAG_URI)) != NULL) {
        ippAddString (job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, ippGetString (attr, 0, NULL));
    } else {
        char printer_uri[1024]; /* job-printer-uri value */

        httpAssembleURI (HTTP_URI_CODING_ALL, printer_uri, sizeof (printer_uri), "ipp", NULL, client->printer->hostname,
            client->printer->port, "/ipp/print");
        ippAddString (job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, printer_uri);
    }

    ippAddInteger (job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation",
        (int)(job->created - client->printer->start_time));

    cupsArrayAdd (client->printer->jobs, job);
    client->printer->active_job = job;

    _cupsRWUnlock (&(client->printer->rwlock));

    return (job);
}

/*
 * 'create_job_file()' - Create a file for the document in a job.
 */

static int                                /* O - File descriptor or -1 on error */
create_job_file (ippeve_job_t *job,       /* I - Job */
    char                      *fname,     /* I - Filename buffer */
    size_t                     fnamesize, /* I - Size of filename buffer */
    const char                *directory, /* I - Directory to store in */
    const char                *ext)                      /* I - Extension (`NULL` for default) */
{
    char name[256],       /* "Safe" filename */
        *nameptr;         /* Pointer into filename */
    const char *job_name; /* job-name value */

    /*
     * Make a name from the job-name attribute...
     */

    if ((job_name = ippGetString (ippFindAttribute (job->attrs, "job-name", IPP_TAG_NAME), 0, NULL)) == NULL)
        job_name = "untitled";

    for (nameptr = name; *job_name && nameptr < (name + sizeof (name) - 1); job_name++) {
        if (isalnum (*job_name & 255) || *job_name == '-') {
            *nameptr++ = (char)tolower (*job_name & 255);
        } else {
            *nameptr++ = '_';

            while (job_name[1] && !isalnum (job_name[1] & 255) && job_name[1] != '-')
                job_name++;
        }
    }

    *nameptr = '\0';

    /*
     * Figure out the extension...
     */

    if (!ext) {
        if (!strcasecmp (job->format, "image/jpeg"))
            ext = "jpg";
        else if (!strcasecmp (job->format, "image/png"))
            ext = "png";
        else if (!strcasecmp (job->format, "image/pwg-raster"))
            ext = "pwg";
        else if (!strcasecmp (job->format, "image/urf"))
            ext = "urf";
        else if (!strcasecmp (job->format, "application/pdf"))
            ext = "pdf";
        else if (!strcasecmp (job->format, "application/postscript"))
            ext = "ps";
        else if (!strcasecmp (job->format, "application/vnd.hp-pcl"))
            ext = "pcl";
        else
            ext = "dat";
    }

    /*
     * Create a filename with the job-id, job-name, and document-format (extension)...
     */

    snprintf (fname, fnamesize, "%s/%d-%s.%s", directory, job->id, name, ext);

    return (open (fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
}

/*
 * 'create_listener()' - Create a listener socket.
 */

static int                         /* O - Listener socket or -1 on error */
create_listener (const char *name, /* I - Host name (`NULL` for any address) */
    int                      port, /* I - Port number */
    int                      family)                    /* I - Address family */
{
    int              sock;         /* Listener socket */
    http_addrlist_t *addrlist;     /* Listen address */
    char             service[255]; /* Service port */

    snprintf (service, sizeof (service), "%d", port);
    if ((addrlist = httpAddrGetList (name, family, service)) == NULL)
        return (-1);

    sock = httpAddrListen (&(addrlist->addr), port);

    httpAddrFreeList (addrlist);

    return (sock);
}

/*
 * 'create_media_col()' - Create a media-col value.
 */

static ipp_t *                        /* O - media-col collection */
create_media_col (const char *media,  /* I - Media name */
    const char               *source, /* I - Media source, if any */
    const char               *type,   /* I - Media type, if any */
    int                       width,  /* I - x-dimension in 2540ths */
    int                       length, /* I - y-dimension in 2540ths */
    int                       bottom, /* I - Bottom margin in 2540ths */
    int                       left,   /* I - Left margin in 2540ths */
    int                       right,  /* I - Right margin in 2540ths */
    int                       top)                          /* I - Top margin in 2540ths */
{
    ipp_t *media_col = ippNew (), /* media-col value */
        *media_size = create_media_size (width, length);
    /* media-size value */
    char        media_key[256];        /* media-key value */
    const char *media_key_suffix = ""; /* media-key suffix */

    if (bottom == 0 && left == 0 && right == 0 && top == 0)
        media_key_suffix = "_borderless";

    if (type && source)
        snprintf (media_key, sizeof (media_key), "%s_%s_%s%s", media, source, type, media_key_suffix);
    else if (type)
        snprintf (media_key, sizeof (media_key), "%s__%s%s", media, type, media_key_suffix);
    else if (source)
        snprintf (media_key, sizeof (media_key), "%s_%s%s", media, source, media_key_suffix);
    else
        snprintf (media_key, sizeof (media_key), "%s%s", media, media_key_suffix);

    ippAddString (media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, media_key);
    ippAddCollection (media_col, IPP_TAG_PRINTER, "media-size", media_size);
    ippAddString (media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-size-name", NULL, media);
    if (bottom >= 0)
        ippAddInteger (media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", bottom);
    if (left >= 0)
        ippAddInteger (media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left);
    if (right >= 0)
        ippAddInteger (media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", right);
    if (top >= 0)
        ippAddInteger (media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top);
    if (source)
        ippAddString (media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source", NULL, source);
    if (type)
        ippAddString (media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", NULL, type);

    ippDelete (media_size);

    return (media_col);
}

/*
 * 'create_media_size()' - Create a media-size value.
 */

static ipp_t *                /* O - media-col collection */
create_media_size (int width, /* I - x-dimension in 2540ths */
    int                length)               /* I - y-dimension in 2540ths */
{
    ipp_t *media_size = ippNew (); /* media-size value */

    ippAddInteger (media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", width);
    ippAddInteger (media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", length);

    return (media_size);
}

static ipp_t *create_media_custom_size (int x_lower, int x_upper, int y_lower, int y_upper) {
    ipp_t *media_col = ippNew (); /* media-col value */

    {
        ipp_t *media_size = ippNew ();
        ippAddInteger (media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "lower", x_lower);
        ippAddInteger (media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "upper", x_upper);
        ippAddCollection (media_col, IPP_TAG_PRINTER, "x-dimension", media_size);
        ippDelete (media_size);
    }
    {
        ipp_t *media_size = ippNew ();
        ippAddInteger (media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "lower", y_lower);
        ippAddInteger (media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "upper", y_upper);
        ippAddCollection (media_col, IPP_TAG_PRINTER, "y-dimension", media_size);
        ippDelete (media_size);
    }
    return media_col;
}

char *replace_char (char *s, char src, char dst) {
    char *olds = s;
    while (*s) {
        if (*s == src)
            *s = dst;
        s++;
    }
    return olds;
}

/*
 * 'create_printer()' - Create, register, and listen for connections to a
 *                      printer object.
 */

static ippeve_printer_t *                  /* O - Printer */
create_printer (const char *servername,    /* I - Server hostname (NULL for default) */
    int                     serverport,    /* I - Server port */
    const char             *name,          /* I - printer-name */
    const char             *location,      /* I - printer-location */
    const char             *icons,         /* I - printer-icons */
    const char             *strings,       /* I - printer-strings-uri */
    cups_array_t           *docformats,    /* I - document-format-supported */
    const char             *subtypes,      /* I - DNS-SD service subtype(s) */
    const char             *directory,     /* I - Spool directory */
    const char             *command,       /* I - Command to run on job files, if any */
    const char             *device_uri,    /* I - Output device, if any */
    const char             *output_format, /* I - Output format, if any */
    ipp_t                  *attrs)                          /* I - Capability attributes */
{
    ippeve_printer_t *printer; /* Printer */
    int               i;       /* Looping var */
#ifndef _WIN32
    char path[1024];                 /* Full path to command */
#endif                               /* !_WIN32 */
    unsigned char sha256[32];        /* SHA-256 digest/sum */
    char          uuid_data[1024],   /* Data to hash for printer-uuid */
        uuid[128],                   /* printer-uuid */
        *iconsptr;                   /* Pointer into icons string */
    int         k_supported;         /* Maximum file size supported */
    int         num_formats;         /* Number of supported document formats */
    const char *formats[100],        /* Supported document formats */
        *format;                     /* Current format */
    int              num_sup_attrs;  /* Number of supported attributes */
    const char      *sup_attrs[100]; /* Supported attributes */
    char             xxx_supported[256];
    /* Name of -supported attribute */
    _cups_globals_t *cg = _cupsGlobals ();
    /* Global path values */
#ifdef HAVE_STATVFS
    struct statvfs spoolinfo; /* FS info for spool directory */
    double         spoolsize; /* FS size */
#elif defined(HAVE_STATFS)
    struct statfs spoolinfo; /* FS info for spool directory */
    double        spoolsize; /* FS size */
#endif                                    /* HAVE_STATVFS */
    static const char *const versions[] = /* ipp-versions-supported values */
        {"1.1", "2.0"};
    static const char *const features[] = /* ipp-features-supported values */
        {
            "airprint-2.1"
            //"ipp-everywhere"
        };
    static const int ops[] = /* operations-supported values */
        {IPP_OP_PRINT_JOB, IPP_OP_PRINT_URI, IPP_OP_VALIDATE_JOB, IPP_OP_CREATE_JOB, IPP_OP_SEND_DOCUMENT,
            IPP_OP_SEND_URI, IPP_OP_CANCEL_JOB, IPP_OP_GET_JOB_ATTRIBUTES, IPP_OP_GET_JOBS,
            IPP_OP_GET_PRINTER_ATTRIBUTES, IPP_OP_CANCEL_MY_JOBS, IPP_OP_CLOSE_JOB, IPP_OP_IDENTIFY_PRINTER};
    static const char *const charsets[] = /* charset-supported values */
        {"us-ascii", "utf-8"};
    static const char *const compressions[] = /* compression-supported values */
        {
#ifdef HAVE_LIBZ
            "deflate", "gzip",
#endif /* HAVE_LIBZ */
            "none"};
    static const char *const identify_actions[] = {"display", "sound"};
    static const char *const job_creation[] = {/* job-creation-attributes-supported values */
        "copies", "document-access", "document-charset", "document-format", "document-message", "document-metadata",
        "document-name", "document-natural-language", "document-password", "finishings", "finishings-col",
        "ipp-attribute-fidelity", "job-account-id", "job-account-type", "job-accouunting-sheets",
        "job-accounting-user-id", "job-authorization-uri", "job-error-action", "job-error-sheet", "job-hold-until",
        "job-hold-until-time", "job-mandatory-attributes", "job-message-to-operator", "job-name", "job-pages-per-set",
        "job-password", "job-password-encryption", "job-phone-number", "job-priority", "job-recipient-name",
        "job-resource-ids", "job-sheet-message", "job-sheets", "job-sheets-col", "media", "media-col",
        "multiple-document-handling", "number-up", "orientation-requested", "output-bin", "output-device", "overrides",
        "page-delivery", "page-ranges", "presentation-direction-number-up", "print-color-mode",
        "print-content-optimize", "print-quality", "print-rendering-intent", "print-scaling", "printer-resolution",
        "proof-print", "separator-sheets", "sides", "x-image-position", "x-image-shift", "x-side1-image-shift",
        "x-side2-image-shift", "y-image-position", "y-image-shift", "y-side1-image-shift", "y-side2-image-shift"};
    static const char *const print_scaling[] = {/* print-scaling-supported values */
        "auto", "auto-fit", "fill", "fit", "none"};

    static const char *const media_col_supported[] = {/* media-col-supported values */
        "media-bottom-margin", "media-left-margin", "media-right-margin", "media-size", "media-size-name",
        "media-source", "media-top-margin", "media-type"};
    static const char *const multiple_document_handling[] = {/* multiple-document-handling-supported values */
        "separate-documents-uncollated-copies", "separate-documents-collated-copies"};
    static const char *const reference_uri_schemes_supported[] = {/* reference-uri-schemes-supported */
        "file", "ftp", "http"
#ifdef HAVE_TLS
        ,
        "https"
#endif /* HAVE_TLS */
    };
#ifdef HAVE_TLS
    static const char *const uri_authentication_supported[] = {/* uri-authentication-supported values */
        "none", "none"};
    static const char *const uri_authentication_basic[] = {/* uri-authentication-supported values with authentication */
        "basic", "basic"};
    static const char *const uri_security_supported[] = {/* uri-security-supported values */
        "none", "tls"};
#endif                                       /* HAVE_TLS */
    static const char *const which_jobs[] = {/* which-jobs-supported values */
        "completed", "not-completed", "aborted", "all", "canceled", "pending", "pending-held", "processing",
        "processing-stopped"};

#ifndef _WIN32
    /*
     * If a command was specified, make sure it exists and is executable...
     */

    if (command) {
        if (*command == '/' || !strncmp (command, "./", 2)) {
            if (access (command, X_OK)) {
                _cupsLangPrintf (stderr, _ ("Unable to execute command \"%s\": %s"), command, strerror (errno));
                return (NULL);
            }
        } else {
            snprintf (path, sizeof (path), "%s/command/%s", cg->cups_serverbin, command);

            if (access (path, X_OK)) {
                _cupsLangPrintf (stderr, _ ("Unable to execute command \"%s\": %s"), path, strerror (errno));
                return (NULL);
            }

            command = path;
        }
    }
#endif /* !_WIN32 */

    /*
     * Allocate memory for the printer...
     */

    if ((printer = calloc (1, sizeof (ippeve_printer_t))) == NULL) {
        _cupsLangPrintError (NULL, _ ("Unable to allocate memory for printer"));
        return (NULL);
    }

    printer->ipv4 = -1;
    printer->ipv6 = -1;
    printer->name = strdup (name);
    printer->dnssd_name = replace_char (strdup (name), '_', ' ');
    printer->dnssd_subtypes = subtypes ? strdup (subtypes) : NULL;
    printer->command = command ? strdup (command) : NULL;
    printer->device_uri = device_uri ? strdup (device_uri) : NULL;
    printer->output_format = output_format ? strdup (output_format) : NULL;
    printer->directory = strdup (directory);
    printer->icons[0] = icons ? strdup (icons) : NULL;
    printer->strings = strings ? strdup (strings) : NULL;
    printer->port = serverport;
    printer->start_time = time (NULL);
    printer->config_time = printer->start_time;
    printer->state = IPP_PSTATE_IDLE;
    printer->state_reasons = IPPEVE_PREASON_NONE;
    printer->state_time = printer->start_time;
    printer->jobs = cupsArrayNew ((cups_array_func_t)compare_jobs, NULL);
    printer->next_job_id = 1;

    if (printer->icons[0]) {
        /*
         * Extract up to 3 icons...
         */

        for (i = 1, iconsptr = strchr (printer->icons[0], ','); iconsptr && i < 3;
             i++, iconsptr = strchr (iconsptr, ',')) {
            *iconsptr++ = '\0';
            printer->icons[i] = iconsptr;
        }

        if (iconsptr)
            *iconsptr = '\0'; /* Strip any icons after the third... */

        while (i < 3) {
            printer->icons[i] = printer->icons[i - 1];
            i++;
        }
    }

    if (servername) {
        printer->hostname = strdup (servername);
    } else {
        char temp[1024], /* Temporary string */
            *tempptr;    /* Pointer into temporary string */

#ifdef HAVE_AVAHI
        const char *avahi_name = avahi_client_get_host_name_fqdn (DNSSDClient);

        if (avahi_name)
            strlcpy (temp, avahi_name, sizeof (temp));
        else
#endif /* HAVE_AVAHI */

            if ((tempptr = strstr (httpGetHostname (NULL, temp, sizeof (temp)), ".lan")) != NULL && !tempptr[5])
                strlcpy (tempptr, ".local", sizeof (temp) - (size_t)(tempptr - temp));

        printer->hostname = strdup (temp);
    }

    _cupsRWInit (&(printer->rwlock));

    /*
     * Create the listener sockets...
     */

    if (printer->port) {
        if ((printer->ipv4 = create_listener (servername, printer->port, AF_INET)) < 0) {
            perror ("Unable to create IPv4 listener");
            goto bad_printer;
        }
    } else {
#ifdef _WIN32
        /*
         * Windows is almost always used as a single user system, so use a default
         * port number of 8631.
         */

        serverport = 8631;

#else
        /*
         * Use 8000 + UID mod 1000 for the default port number...
         */

        serverport = 8000 + ((int)getuid () % 1000);
#endif /* _WIN32 */

        while (serverport < 10000) {
            if ((printer->ipv4 = create_listener (servername, serverport, AF_INET)) >= 0)
                break;

            serverport++;
        }

        if (serverport < 10000) {
            _cupsLangPrintf (stderr, _ ("Listening on port %d."), serverport);
            printer->port = serverport;
        } else {
            perror ("Unable to create IPv4 listener");
            goto bad_printer;
        }
    }

    if ((printer->ipv6 = create_listener (servername, printer->port, AF_INET6)) < 0) {
        perror ("Unable to create IPv6 listener");
        goto bad_printer;
    }

    /*
     * Prepare values for the printer attributes...
     */

    snprintf (
        uuid_data, sizeof (uuid_data), "_IPPEVEPRINTER_:%s:%d:%s", printer->hostname, printer->port, printer->name);
    cupsHashData ("sha2-256", (unsigned char *)uuid_data, strlen (uuid_data), sha256, sizeof (sha256));
#if 0
    snprintf(uuid, sizeof(uuid), "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", sha256[0], sha256[1], sha256[3], sha256[4], sha256[5], sha256[6], (sha256[10] & 15) | 0x30, sha256[11], (sha256[15] & 0x3f) | 0x40, sha256[16], sha256[20], sha256[21], sha256[25], sha256[26], sha256[30], sha256[31]);
#else
    strcpy (uuid, "urn:uuid:93b96dd5-32f0-3f52-7b2f-1f90be48231d");
#endif
    if (Verbosity) {
#ifdef HAVE_TLS
        fprintf (stderr, "printer-uri-supported=\"ipp://%s:%d/ipp/print\",\"ipps://%s:%d/ipp/print\"\n",
            printer->hostname, printer->port, printer->hostname, printer->port);
#else
        fprintf (stderr, "printer-uri-supported=\"ipp://%s:%d/ipp/print\"\n", printer->hostname, printer->port);
#endif /* HAVE_TLS */
        fprintf (stderr, "printer-uuid=\"%s\"\n", uuid);
    }

    /*
     * Get the maximum spool size based on the size of the filesystem used for
     * the spool directory.  If the host OS doesn't support the statfs call
     * or the filesystem is larger than 2TiB, always report INT_MAX.
     */

#ifdef HAVE_STATVFS
    if (statvfs (printer->directory, &spoolinfo))
        k_supported = INT_MAX;
    else if ((spoolsize = (double)spoolinfo.f_frsize * spoolinfo.f_blocks / 1024) > INT_MAX)
        k_supported = INT_MAX;
    else
        k_supported = (int)spoolsize;

#elif defined(HAVE_STATFS)
    if (statfs (printer->directory, &spoolinfo))
        k_supported = INT_MAX;
    else if ((spoolsize = (double)spoolinfo.f_bsize * spoolinfo.f_blocks / 1024) > INT_MAX)
        k_supported = INT_MAX;
    else
        k_supported = (int)spoolsize;

#else
    k_supported = INT_MAX;
#endif /* HAVE_STATVFS */

    /*
     * Assemble the final list of document formats...
     */

    if (!cupsArrayFind (docformats, (void *)"application/octet-stream"))
        cupsArrayAdd (docformats, (void *)"application/octet-stream");

    for (num_formats = 0, format = (const char *)cupsArrayFirst (docformats);
         format && num_formats < (int)(sizeof (formats) / sizeof (formats[0]));
         format = (const char *)cupsArrayNext (docformats))
        formats[num_formats++] = format;

    /*
     * Get the list of attributes that can be used when creating a job...
     */

    num_sup_attrs = 0;
    sup_attrs[num_sup_attrs++] = "media-col";
    sup_attrs[num_sup_attrs++] = "multiple-document-handling";
    sup_attrs[num_sup_attrs++] = "document-access";
    sup_attrs[num_sup_attrs++] = "document-charset";
    sup_attrs[num_sup_attrs++] = "document-format";
    sup_attrs[num_sup_attrs++] = "document-message";
    sup_attrs[num_sup_attrs++] = "document-metadata";
    sup_attrs[num_sup_attrs++] = "document-name";
    sup_attrs[num_sup_attrs++] = "document-natural-language";
    sup_attrs[num_sup_attrs++] = "ipp-attribute-fidelity";
    sup_attrs[num_sup_attrs++] = "job-name";
    sup_attrs[num_sup_attrs++] = "job-priority";
#if 1
    for (i = 0; i < (int)(sizeof (job_creation) / sizeof (job_creation[0]))
                && num_sup_attrs < (int)(sizeof (sup_attrs) / sizeof (sup_attrs[0]));
         i++) {
        snprintf (xxx_supported, sizeof (xxx_supported), "%s-supported", job_creation[i]);
        if (ippFindAttribute (attrs, xxx_supported, IPP_TAG_ZERO))
            sup_attrs[num_sup_attrs++] = job_creation[i];
    }
#else
    for (i = 0; i < (int)(sizeof (job_creation) / sizeof (job_creation[0])); i++) {
        snprintf (xxx_supported, sizeof (xxx_supported), "%s-supported", job_creation[i]);
        if (ippFindAttribute (attrs, xxx_supported, IPP_TAG_ZERO)) {
            sup_attrs[num_sup_attrs++] = job_creation[i];
        } else {
            printf ("Not support: %s\n", xxx_supported);
        }
    }
#endif
    /*
     * Fill out the rest of the printer attributes.
     */

    printer->attrs = attrs;

    /* charset-configured */
    ippAddString (
        printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_CHARSET), "charset-configured", NULL, "utf-8");

    /* charset-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_CHARSET), "charset-supported",
        sizeof (charsets) / sizeof (charsets[0]), NULL, charsets);

    /* compression-supported */
    if (!ippFindAttribute (printer->attrs, "compression-supported", IPP_TAG_ZERO))
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "compression-supported",
            (int)(sizeof (compressions) / sizeof (compressions[0])), NULL, compressions);

    /* document-format-default */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_MIMETYPE), "document-format-default", NULL,
        "application/octet-stream");

    /* document-format-supported */
    ippAddStrings (
        printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, "document-format-supported", num_formats, NULL, formats);

    /* generated-natural-language-supported */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_LANGUAGE),
        "generated-natural-language-supported", NULL, "en");

    /* identify-actions-default */
    ippAddString (
        printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "identify-actions-default", NULL, "sound");

    /* identify-actions-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "identify-actions-supported",
        sizeof (identify_actions) / sizeof (identify_actions[0]), NULL, identify_actions);

    /* ipp-features-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "ipp-features-supported",
        sizeof (features) / sizeof (features[0]), NULL, features);

    /* ipp-versions-supported */
    if (MaxVersion == 11)
        ippAddString (
            printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "ipp-versions-supported", NULL, "1.1");
    else
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "ipp-versions-supported",
            (int)(sizeof (versions) / sizeof (versions[0])), NULL, versions);

    /* job-creation-attributes-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
        "job-creation-attributes-supported", num_sup_attrs, NULL, sup_attrs);

    /* job-ids-supported */
    ippAddBoolean (printer->attrs, IPP_TAG_PRINTER, "job-ids-supported", 1);

    /* job-k-octets-supported */
    ippAddRange (printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0, k_supported);

    /* job-priority-default */
    ippAddInteger (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-default", 50);

    /* job-priority-supported */
    ippAddInteger (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-supported", 1);

    /* job-sheets-default */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_NAME), "job-sheets-default", NULL, "none");

    /* job-sheets-supported */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_NAME), "job-sheets-supported", NULL, "none");

    /* media-col-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-col-supported",
        (int)(sizeof (media_col_supported) / sizeof (media_col_supported[0])), NULL, media_col_supported);

    /* multiple-document-handling-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
        "multiple-document-handling-supported",
        sizeof (multiple_document_handling) / sizeof (multiple_document_handling[0]), NULL, multiple_document_handling);

    /* multiple-document-jobs-supported */
    ippAddBoolean (printer->attrs, IPP_TAG_PRINTER, "multiple-document-jobs-supported", 0);

    /* multiple-operation-time-out */
    ippAddInteger (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "multiple-operation-time-out", 60);

    /* multiple-operation-time-out-action */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
        "multiple-operation-time-out-action", NULL, "abort-job");

    /* natural-language-configured */
    ippAddString (
        printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_LANGUAGE), "natural-language-configured", NULL, "en");

    /* operations-supported */
    ippAddIntegers (
        printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "operations-supported", sizeof (ops) / sizeof (ops[0]), ops);

    /* pdl-override-supported */
    ippAddString (
        printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "pdl-override-supported", NULL, "attempted");

    /* preferred-attributes-supported */
    ippAddBoolean (printer->attrs, IPP_TAG_PRINTER, "preferred-attributes-supported", 0);

    /* printer-get-attributes-supported */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "printer-get-attributes-supported",
        NULL, "document-format");

    /* printer-geo-location */
    ippAddOutOfBand (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location");

    /* printer-is-accepting-jobs */
    ippAddBoolean (printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1);

    /* printer-info */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL, name);

    /* printer-location */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location", NULL, location);

    /* printer-name */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL, name);

    /* printer-organization */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-organization", NULL, "");

    /* printer-organizational-unit */
    ippAddString (
        printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-organizational-unit", NULL, "");

    /* printer-strings-languages-supported */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE, "printer-strings-languages-supported", NULL, "en");

    /* printer-uuid */
    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uuid", NULL, uuid);

    /* reference-uri-scheme-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_URISCHEME),
        "reference-uri-schemes-supported",
        (int)(sizeof (reference_uri_schemes_supported) / sizeof (reference_uri_schemes_supported[0])), NULL,
        reference_uri_schemes_supported);

    /* uri-authentication-supported */
#ifdef HAVE_TLS
    if (PAMService)
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "uri-authentication-supported",
            2, NULL, uri_authentication_basic);
    else
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "uri-authentication-supported",
            2, NULL, uri_authentication_supported);
#else
    if (PAMService)
        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "uri-authentication-supported",
            NULL, "basic");
    else
        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "uri-authentication-supported",
            NULL, "none");
#endif /* HAVE_TLS */

        /* uri-security-supported */
#ifdef HAVE_TLS
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "uri-security-supported", 2, NULL,
        uri_security_supported);
#else
    ippAddString (
        printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "uri-security-supported", NULL, "none");
#endif /* HAVE_TLS */

    /* which-jobs-supported */
    ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "which-jobs-supported",
        sizeof (which_jobs) / sizeof (which_jobs[0]), NULL, which_jobs);
    {
        struct statfs spoolinfo; /* FS info for spool directory */
        double        spoolsize; /* FS size */
        int           k_supported;
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-scaling-supported",
            sizeof (print_scaling) / sizeof (print_scaling[0]), NULL, print_scaling);
        ippAddString (
            printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-scaling-default", NULL, "auto");

        if (statfs ("/tmp", &spoolinfo))
            k_supported = INT_MAX;
        else if ((spoolsize = (double)spoolinfo.f_bsize * spoolinfo.f_blocks / 1024) > INT_MAX)
            k_supported = INT_MAX;
        else
            k_supported = (int)spoolsize;
        /* jpeg-k-octets-supported */
        ippAddRange (printer->attrs, IPP_TAG_PRINTER, "jpeg-k-octets-supported", 0, k_supported);
        ippAddRange (printer->attrs, IPP_TAG_PRINTER, "pdf-k-octets-supported", 0, k_supported);

        /* jpeg-x-dimension-supported */
        ippAddRange (printer->attrs, IPP_TAG_PRINTER, "jpeg-x-dimension-supported", 0, 65535);
        /* jpeg-y-dimension-supported */
        ippAddRange (printer->attrs, IPP_TAG_PRINTER, "jpeg-y-dimension-supported", 1, 65535);
        ippAddInteger (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "landscape-orientation-requested-preferred",
            IPP_ORIENT_LANDSCAPE);

        {
            static const char *const jpeg_feature_supported[]
                = {"none", "arithmetic", "cmyk", "deep", "icc", "lossless", "progressive"};
            ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "jpeg-features-supported",
                sizeof (jpeg_feature_supported) / sizeof (jpeg_feature_supported[0]), NULL, jpeg_feature_supported);
        }
        //  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "jpeg-features-supported", 1);

#if 0
        char *mark_colors[] = {
            "#00FFFF",
            "#FF00FF",
            "#FFFF00",
            "#000000",
        };

        int mark_levels[] = {
            29,
            62,
            45,
            17
        };
        int marker_low_levels[] = {
            0,
            10,
            20,
            30,
        };
        int marker_high_levels[] = {
            100,
            90,
            80,
            70,
        };
        char *marker_names[]={
            "Cyan",
            "Magenta",
            "Yellow",
            "Black",
        };
        char *marker_types[]={
            "toner-cartridge",
            "toner-cartridge",
            "toner-cartridge",
            "toner-cartridge"
        };
#else
        const char *mark_colors[] = {"none"};

        int mark_levels[] = {
            29,
        };
        int marker_low_levels[] = {
            0,
        };
        int marker_high_levels[] = {
            90,
        };
        const char *marker_names[] = {"Black"};
        const char *marker_types[] = {"toner"};
#endif
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "marker-colors",
            sizeof (mark_colors) / sizeof (mark_colors[0]), NULL, mark_colors);

        ippAddIntegers (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "marker-levels",
            sizeof (mark_levels) / sizeof (mark_levels[0]), mark_levels);
        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "marker-message", NULL, "abcd");
        ippAddIntegers (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "marker-low-levels",
            sizeof (marker_low_levels) / sizeof (marker_low_levels[0]), marker_low_levels);
        ippAddIntegers (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "marker-high-levels",
            sizeof (marker_high_levels) / sizeof (marker_high_levels[0]), marker_high_levels);

        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "marker-names",
            sizeof (marker_names) / sizeof (marker_names[0]), NULL, marker_names);
        ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "marker-types",
            sizeof (marker_types) / sizeof (marker_types[0]), NULL, marker_types);

        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-firmware-name", NULL, printer->name);
        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-firmware-string-version",
            NULL, "v1.0");
        ippAddString (
            printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_STRING), "printer-firmware-version", NULL, "1");
        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-dns-sd-name", NULL, printer->dnssd_name);

        if (0) {
            const char *kind[]
                = {"disc", "document", "envelope", "label", "large-format", "photo", "postcard", "receiption", "roll"

                };
            ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "printer-kind",
                sizeof (kind) / sizeof (kind[0]), NULL, kind);
        }
        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "printer-kind", NULL, "label");

        ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
            "multiple-document-handling-default", NULL, "separate-documents-uncollated-copies");
        {
            const char *const pdf_versions[] = {/* pdf-versions-supported */
                "adobe-1.2", "adobe-1.3", "adobe-1.4", "adobe-1.5", "adobe-1.6", "adobe-1.7", "iso-19005-1_2005",
                "iso-32000-1_2008", "pwg-5102.3"};
            ippAddStrings (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "pdf-versions-supported",
                sizeof (pdf_versions) / sizeof (pdf_versions[0]), NULL, pdf_versions);
        }
    }
    debug_attributes ("Printer", printer->attrs, 0);

    /*
     * Register the printer with DNS-SD...
     */

    if (!register_printer (printer))
        goto bad_printer;

    /*
     * Return it!
     */

    return (printer);

    /*
     * If we get here we were unable to create the printer...
     */

bad_printer:

    delete_printer (printer);

    return (NULL);
}

/*
 * 'debug_attributes()' - Print attributes in a request or response.
 */

static void debug_attributes (const char *title, /* I - Title */
    ipp_t                                *ipp,   /* I - Request/response */
    int                                   type)                                    /* I - 0 = object, 1 = request, 2 = response */
{
    ipp_tag_t        group_tag;    /* Current group */
    ipp_attribute_t *attr;         /* Current attribute */
    char             buffer[2048]; /* String buffer for value */
    int              major, minor; /* Version */

    if (Verbosity <= 1)
        return;

    fprintf (stderr, "%s:\n", title);
    major = ippGetVersion (ipp, &minor);
    fprintf (stderr, "  version=%d.%d\n", major, minor);
    if (type == 1)
        fprintf (stderr, "  operation-id=%s(%04x)\n", ippOpString (ippGetOperation (ipp)), ippGetOperation (ipp));
    else if (type == 2)
        fprintf (stderr, "  status-code=%s(%04x)\n", ippErrorString (ippGetStatusCode (ipp)), ippGetStatusCode (ipp));
    fprintf (stderr, "  request-id=%d\n\n", ippGetRequestId (ipp));

    for (attr = ippFirstAttribute (ipp), group_tag = IPP_TAG_ZERO; attr; attr = ippNextAttribute (ipp)) {
        if (ippGetGroupTag (attr) != group_tag) {
            group_tag = ippGetGroupTag (attr);
            fprintf (stderr, "  %s\n", ippTagString (group_tag));
        }

        if (ippGetName (attr)) {
            ippAttributeString (attr, buffer, sizeof (buffer));
            fprintf (stderr, "    %s (%s%s) %s\n", ippGetName (attr), ippGetCount (attr) > 1 ? "1setOf " : "",
                ippTagString (ippGetValueTag (attr)), buffer);
        }
    }
}

/*
 * 'delete_client()' - Close the socket and free all memory used by a client
 *                     object.
 */

static void delete_client (ippeve_client_t *client) /* I - Client */
{
    if (Verbosity)
        fprintf (stderr, "Closing connection from %s\n", client->hostname);

    /*
     * Flush pending writes before closing...
     */

    httpFlushWrite (client->http);

    /*
     * Free memory...
     */

    httpClose (client->http);

    ippDelete (client->request);
    ippDelete (client->response);

    free (client);
}

/*
 * 'delete_job()' - Remove from the printer and free all memory used by a job
 *                  object.
 */

static void delete_job (ippeve_job_t *job) /* I - Job */
{
    if (Verbosity)
        fprintf (stderr, "[Job %d] Removing job from history.\n", job->id);

    ippDelete (job->attrs);

    if (job->message)
        free (job->message);

    if (job->filename) {
        if (!KeepFiles)
            unlink (job->filename);

        free (job->filename);
    }

    free (job);
}

/*
 * 'delete_printer()' - Unregister, close listen sockets, and free all memory
 *                      used by a printer object.
 */

static void delete_printer (ippeve_printer_t *printer) /* I - Printer */
{
    if (printer->ipv4 >= 0)
        close (printer->ipv4);

    if (printer->ipv6 >= 0)
        close (printer->ipv6);

#if HAVE_MDNSRESPONDER
    if (printer->printer_ref)
        DNSServiceRefDeallocate (printer->printer_ref);
    if (printer->ipp_ref)
        DNSServiceRefDeallocate (printer->ipp_ref);
    if (printer->ipps_ref)
        DNSServiceRefDeallocate (printer->ipps_ref);
    if (printer->http_ref)
        DNSServiceRefDeallocate (printer->http_ref);
#elif defined(HAVE_AVAHI)
    avahi_threaded_poll_lock (DNSSDMaster);

    if (printer->dnssd_ref)
        avahi_entry_group_free (printer->dnssd_ref);

    avahi_threaded_poll_unlock (DNSSDMaster);
#endif /* HAVE_MDNSRESPONDER */

    if (printer->dnssd_name)
        free (printer->dnssd_name);
    if (printer->name)
        free (printer->name);
    if (printer->icons[0])
        free (printer->icons[0]);
    if (printer->strings)
        free (printer->strings);
    if (printer->command)
        free (printer->command);
    if (printer->device_uri)
        free (printer->device_uri);
#if !CUPS_LITE
    if (printer->ppdfile)
        free (printer->ppdfile);
#endif /* !CUPS_LITE */
    if (printer->directory)
        free (printer->directory);
    if (printer->hostname)
        free (printer->hostname);

    ippDelete (printer->attrs);
    cupsArrayDelete (printer->jobs);

    free (printer);
}

#ifdef HAVE_MDNSRESPONDER
/*
 * 'dnssd_callback()' - Handle DNS-SD registration events.
 */

static void DNSSD_API dnssd_callback (DNSServiceRef sdRef,     /* I - Service reference */
    DNSServiceFlags                                 flags,     /* I - Status flags */
    DNSServiceErrorType                             errorCode, /* I - Error, if any */
    const char                                     *name,      /* I - Service name */
    const char                                     *regtype,   /* I - Service type */
    const char                                     *domain,    /* I - Domain for service */
    ippeve_printer_t                               *printer)                                 /* I - Printer */
{
    (void)sdRef;
    (void)flags;
    (void)domain;
    (void)name;

    if (errorCode == kDNSServiceErr_NameConflict) {
        fputs ("DNS-SD service name collision detected.\n", stderr);
        printer->dnssd_collision = 1;
    } else if (errorCode) {
        fprintf (stderr, "DNSServiceRegister for %s failed with error %d.\n", regtype, (int)errorCode);
        return;
    }
}

#elif defined(HAVE_AVAHI)
/*
 * 'dnssd_callback()' - Handle DNS-SD registration events.
 */

static void dnssd_callback (AvahiEntryGroup *srv,   /* I - Service */
    AvahiEntryGroupState                     state, /* I - Registration state */
    void                                    *context)                                  /* I - Printer */
{
    ippeve_printer_t *printer = (ippeve_printer_t *)context;
    /* Printer */

    (void)srv;

    if (state == AVAHI_ENTRY_GROUP_COLLISION) {
        fputs ("DNS-SD service name collision detected.\n", stderr);
        printer->dnssd_collision = 1;
    }
}

/*
 * 'dnssd_client_cb()' - Client callback for Avahi.
 *
 * Called whenever the client or server state changes...
 */

static void dnssd_client_cb (AvahiClient *c,     /* I - Client */
    AvahiClientState                      state, /* I - Current state */
    void                                 *userdata)                              /* I - User data (printer) */
{
    if (!c)
        return;

    if (state == AVAHI_CLIENT_FAILURE) {
        if (avahi_client_errno (c) == AVAHI_ERR_DISCONNECTED) {
            fputs ("Avahi server crashed, exiting.\n", stderr);
            exit (1);
        }
    } else {
        fprintf (stderr, "Ignored Avahi state %d.\n", state);
    }
}
#endif /* HAVE_MDNSRESPONDER */

/*
 * 'dnssd_init()' - Initialize the DNS-SD service connections...
 */

static void dnssd_init (void) {
#ifdef HAVE_MDNSRESPONDER
    if (DNSServiceCreateConnection (&DNSSDMaster) != kDNSServiceErr_NoError) {
        fputs ("Error: Unable to initialize DNS-SD.\n", stderr);
        exit (1);
    }

#elif defined(HAVE_AVAHI)
    int error; /* Error code, if any */

    if ((DNSSDMaster = avahi_threaded_poll_new ()) == NULL) {
        fputs ("Error: Unable to initialize DNS-SD.\n", stderr);
        exit (1);
    }

    if ((DNSSDClient = avahi_client_new (
             avahi_threaded_poll_get (DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssd_client_cb, NULL, &error))
        == NULL) {
        fputs ("Error: Unable to initialize DNS-SD.\n", stderr);
        exit (1);
    }

    avahi_threaded_poll_start (DNSSDMaster);
#endif /* HAVE_MDNSRESPONDER */
}

/*
 * 'filter_cb()' - Filter printer attributes based on the requested array.
 */

static int                          /* O - 1 to copy, 0 to ignore */
filter_cb (ippeve_filter_t *filter, /* I - Filter parameters */
    ipp_t                  *dst,    /* I - Destination (unused) */
    ipp_attribute_t        *attr)          /* I - Source attribute */
{
    /*
     * Filter attributes as needed...
     */

#ifndef _WIN32 /* Avoid MS compiler bug */
    (void)dst;
#endif /* !_WIN32 */

    ipp_tag_t   group = ippGetGroupTag (attr);
    const char *name = ippGetName (attr);

    if ((filter->group_tag != IPP_TAG_ZERO && group != filter->group_tag && group != IPP_TAG_ZERO) || !name
        || (!strcmp (name, "media-col-database") && !cupsArrayFind (filter->ra, (void *)name)))
        return (0);

    return (!filter->ra || cupsArrayFind (filter->ra, (void *)name) != NULL);
}

/*
 * 'find_job()' - Find a job specified in a request.
 */

static ippeve_job_t *              /* O - Job or NULL */
find_job (ippeve_client_t *client) /* I - Client */
{
    ipp_attribute_t *attr; /* job-id or job-uri attribute */
    ippeve_job_t     key,  /* Job search key */
        *job;              /* Matching job, if any */

    if ((attr = ippFindAttribute (client->request, "job-uri", IPP_TAG_URI)) != NULL) {
        const char *uri = ippGetString (attr, 0, NULL);
        /* URI value */
        const char *uriptr = strrchr (uri, '/');
        /* Pointer to the last slash in the URI */

        if (uriptr && isdigit (uriptr[1] & 255))
            key.id = atoi (uriptr + 1);
        else
            return (NULL);
    } else if ((attr = ippFindAttribute (client->request, "job-id", IPP_TAG_INTEGER)) != NULL)
        key.id = ippGetInteger (attr, 0);

    _cupsRWLockRead (&(client->printer->rwlock));
    job = (ippeve_job_t *)cupsArrayFind (client->printer->jobs, &key);
    _cupsRWUnlock (&(client->printer->rwlock));

    return (job);
}

/*
 * 'finish_document()' - Finish receiving a document file and start processing.
 */

static void finish_document_data (ippeve_client_t *client, /* I - Client */
    ippeve_job_t                                  *job)                                     /* I - Job */
{
    char filename[1024],  /* Filename buffer */
        buffer[4096];     /* Copy buffer */
    ssize_t        bytes; /* Bytes read */
    cups_array_t  *ra;    /* Attributes to send in response */
    _cups_thread_t t;     /* Thread */

    /*
     * Create a file for the request data...
     *
     * TODO: Update code to support piping large raster data to the print command.
     */

    if ((job->fd = create_job_file (job, filename, sizeof (filename), client->printer->directory, NULL)) < 0) {
        respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror (errno));

        goto abort_job;
    }

    if (Verbosity)
        fprintf (stderr, "Created job file \"%s\", format \"%s\".\n", filename, job->format);

    while ((bytes = httpRead2 (client->http, buffer, sizeof (buffer))) > 0) {
        if (write (job->fd, buffer, (size_t)bytes) < bytes) {
            int error = errno; /* Write error */

            close (job->fd);
            job->fd = -1;

            unlink (filename);

            respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror (error));

            goto abort_job;
        }
    }

    if (bytes < 0) {
        /*
         * Got an error while reading the print data, so abort this job.
         */

        close (job->fd);
        job->fd = -1;

        unlink (filename);

        respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to read print file.");

        goto abort_job;
    }

    if (close (job->fd)) {
        int error = errno; /* Write error */

        job->fd = -1;

        unlink (filename);

        respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror (error));

        goto abort_job;
    }

    job->fd = -1;
    job->filename = strdup (filename);
    job->state = IPP_JSTATE_PENDING;

    /*
     * Process the job...
     */

    t = _cupsThreadCreate ((_cups_thread_func_t)process_job, job);

    if (t) {
        _cupsThreadDetach (t);
    } else {
        respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job.");
        goto abort_job;
    }

    /*
     * Return the job info...
     */

    respond_ipp (client, IPP_STATUS_OK, NULL);

    ra = cupsArrayNew ((cups_array_func_t)strcmp, NULL);
    cupsArrayAdd (ra, "job-id");
    cupsArrayAdd (ra, "job-state");
    cupsArrayAdd (ra, "job-state-message");
    cupsArrayAdd (ra, "job-state-reasons");
    cupsArrayAdd (ra, "job-uri");

    copy_job_attributes (client, job, ra);
    cupsArrayDelete (ra);
    return;

    /*
     * If we get here we had to abort the job...
     */

abort_job:

    job->state = IPP_JSTATE_ABORTED;
    job->completed = time (NULL);

    ra = cupsArrayNew ((cups_array_func_t)strcmp, NULL);
    cupsArrayAdd (ra, "job-id");
    cupsArrayAdd (ra, "job-state");
    cupsArrayAdd (ra, "job-state-reasons");
    cupsArrayAdd (ra, "job-uri");

    copy_job_attributes (client, job, ra);
    cupsArrayDelete (ra);
}

/*
 * 'finish_uri()' - Finish fetching a document URI and start processing.
 */

static void finish_document_uri (ippeve_client_t *client, /* I - Client */
    ippeve_job_t                                 *job)                                    /* I - Job */
{
    ipp_attribute_t *uri;             /* document-uri */
    char             scheme[256],     /* URI scheme */
        userpass[256],                /* Username and password info */
        hostname[256],                /* Hostname */
        resource[1024];               /* Resource path */
    int               port;           /* Port number */
    http_uri_status_t uri_status;     /* URI decode status */
    http_encryption_t encryption;     /* Encryption to use, if any */
    http_t           *http;           /* Connection for http/https URIs */
    http_status_t     status;         /* Access status for http/https URIs */
    int               infile;         /* Input file for local file URIs */
    char              filename[1024], /* Filename buffer */
        buffer[4096];                 /* Copy buffer */
    ssize_t          bytes;           /* Bytes read */
    ipp_attribute_t *attr;            /* Current attribute */
    cups_array_t    *ra;              /* Attributes to send in response */

    /*
     * Do we have a file to print?
     */

    if (have_document_data (client)) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Unexpected document data following request.");

        goto abort_job;
    }

    /*
     * Do we have a document URI?
     */

    if ((uri = ippFindAttribute (client->request, "document-uri", IPP_TAG_URI)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing document-uri.");

        goto abort_job;
    }

    if (ippGetCount (uri) != 1) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Too many document-uri values.");

        goto abort_job;
    }

    uri_status = httpSeparateURI (HTTP_URI_CODING_ALL, ippGetString (uri, 0, NULL), scheme, sizeof (scheme), userpass,
        sizeof (userpass), hostname, sizeof (hostname), &port, resource, sizeof (resource));
    if (uri_status < HTTP_URI_STATUS_OK) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad document-uri: %s", httpURIStatusString (uri_status));

        goto abort_job;
    }

    if (strcmp (scheme, "file") &&
#ifdef HAVE_TLS
        strcmp (scheme, "https") &&
#endif /* HAVE_TLS */
        strcmp (scheme, "http")) {
        respond_ipp (client, IPP_STATUS_ERROR_URI_SCHEME, "URI scheme \"%s\" not supported.", scheme);

        goto abort_job;
    }

    if (!strcmp (scheme, "file") && access (resource, R_OK)) {
        respond_ipp (client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI: %s", strerror (errno));

        goto abort_job;
    }

    /*
     * Get the document format for the job...
     */

    _cupsRWLockWrite (&(client->printer->rwlock));

    if ((attr = ippFindAttribute (job->attrs, "document-format", IPP_TAG_MIMETYPE)) != NULL)
        job->format = ippGetString (attr, 0, NULL);
    else
        job->format = "application/octet-stream";

    /*
     * Create a file for the request data...
     */

    if ((job->fd = create_job_file (job, filename, sizeof (filename), client->printer->directory, NULL)) < 0) {
        _cupsRWUnlock (&(client->printer->rwlock));

        respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror (errno));

        goto abort_job;
    }

    _cupsRWUnlock (&(client->printer->rwlock));

    if (!strcmp (scheme, "file")) {
        if ((infile = open (resource, O_RDONLY | O_BINARY)) < 0) {
            respond_ipp (client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI: %s", strerror (errno));

            goto abort_job;
        }

        do {
            if ((bytes = read (infile, buffer, sizeof (buffer))) < 0 && (errno == EAGAIN || errno == EINTR)) {
                bytes = 1;
            } else if (bytes > 0 && write (job->fd, buffer, (size_t)bytes) < bytes) {
                int error = errno; /* Write error */

                close (job->fd);
                job->fd = -1;

                unlink (filename);
                close (infile);

                respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror (error));

                goto abort_job;
            }
        } while (bytes > 0);

        close (infile);
    } else {
#ifdef HAVE_TLS
        if (port == 443 || !strcmp (scheme, "https"))
            encryption = HTTP_ENCRYPTION_ALWAYS;
        else
#endif /* HAVE_TLS */
            encryption = HTTP_ENCRYPTION_IF_REQUESTED;

        if ((http = httpConnect2 (hostname, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL) {
            respond_ipp (client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to connect to %s: %s", hostname,
                cupsLastErrorString ());

            close (job->fd);
            job->fd = -1;

            unlink (filename);

            goto abort_job;
        }

        httpClearFields (http);
        httpSetField (http, HTTP_FIELD_ACCEPT_LANGUAGE, "en");
        if (httpGet (http, resource)) {
            respond_ipp (client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", strerror (errno));

            close (job->fd);
            job->fd = -1;

            unlink (filename);
            httpClose (http);

            goto abort_job;
        }

        while ((status = httpUpdate (http)) == HTTP_STATUS_CONTINUE)
            ;

        if (status != HTTP_STATUS_OK) {
            respond_ipp (client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", httpStatus (status));

            close (job->fd);
            job->fd = -1;

            unlink (filename);
            httpClose (http);

            goto abort_job;
        }

        while ((bytes = httpRead2 (http, buffer, sizeof (buffer))) > 0) {
            if (write (job->fd, buffer, (size_t)bytes) < bytes) {
                int error = errno; /* Write error */

                close (job->fd);
                job->fd = -1;

                unlink (filename);
                httpClose (http);

                respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror (error));

                goto abort_job;
            }
        }

        httpClose (http);
    }

    if (close (job->fd)) {
        int error = errno; /* Write error */

        job->fd = -1;

        unlink (filename);

        respond_ipp (client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror (error));

        goto abort_job;
    }

    _cupsRWLockWrite (&(client->printer->rwlock));

    job->fd = -1;
    job->filename = strdup (filename);
    job->state = IPP_JSTATE_PENDING;

    _cupsRWUnlock (&(client->printer->rwlock));

    /*
     * Process the job...
     */

    process_job (job);

    /*
     * Return the job info...
     */

    respond_ipp (client, IPP_STATUS_OK, NULL);

    ra = cupsArrayNew ((cups_array_func_t)strcmp, NULL);
    cupsArrayAdd (ra, "job-id");
    cupsArrayAdd (ra, "job-state");
    cupsArrayAdd (ra, "job-state-reasons");
    cupsArrayAdd (ra, "job-uri");

    copy_job_attributes (client, job, ra);
    cupsArrayDelete (ra);
    return;

    /*
     * If we get here we had to abort the job...
     */

abort_job:

    job->state = IPP_JSTATE_ABORTED;
    job->completed = time (NULL);

    ra = cupsArrayNew ((cups_array_func_t)strcmp, NULL);
    cupsArrayAdd (ra, "job-id");
    cupsArrayAdd (ra, "job-state");
    cupsArrayAdd (ra, "job-state-reasons");
    cupsArrayAdd (ra, "job-uri");

    copy_job_attributes (client, job, ra);
    cupsArrayDelete (ra);
}

/*
 * 'flush_document_data()' - Safely flush remaining document data.
 */

static void flush_document_data (ippeve_client_t *client) /* I - Client */
{
    char buffer[8192]; /* Read buffer */

    if (httpGetState (client->http) == HTTP_STATE_POST_RECV) {
        while (httpRead2 (client->http, buffer, sizeof (buffer)) > 0)
            ;
    }
}

/*
 * 'have_document_data()' - Determine whether we have more document data.
 */

static int                                   /* O - 1 if data is present, 0 otherwise */
have_document_data (ippeve_client_t *client) /* I - Client */
{
    char temp; /* Data */

    if (httpGetState (client->http) != HTTP_STATE_POST_RECV)
        return (0);
    else
        return (httpPeek (client->http, &temp, 1) > 0);
}

/*
 * 'html_escape()' - Write a HTML-safe string.
 */

static void html_escape (ippeve_client_t *client, /* I - Client */
    const char                           *s,      /* I - String to write */
    size_t                                slen)                                  /* I - Number of characters to write */
{
    const char *start, /* Start of segment */
        *end;          /* End of string */

    start = s;
    end = s + (slen > 0 ? slen : strlen (s));

    while (*s && s < end) {
        if (*s == '&' || *s == '<') {
            if (s > start)
                httpWrite2 (client->http, start, (size_t)(s - start));

            if (*s == '&')
                httpWrite2 (client->http, "&amp;", 5);
            else
                httpWrite2 (client->http, "&lt;", 4);

            start = s + 1;
        }

        s++;
    }

    if (s > start)
        httpWrite2 (client->http, start, (size_t)(s - start));
}

/*
 * 'html_footer()' - Show the web interface footer.
 *
 * This function also writes the trailing 0-length chunk.
 */

static void html_footer (ippeve_client_t *client) /* I - Client */
{
    html_printf (client, "</div>\n"
                         "</body>\n"
                         "</html>\n");
    httpWrite2 (client->http, "", 0);
}

/*
 * 'html_header()' - Show the web interface header and title.
 */

static void html_header (ippeve_client_t *client, /* I - Client */
    const char                           *title,  /* I - Title */
    int                                   refresh)                                  /* I - Refresh timer, if any */
{
    html_printf (client,
        "<!doctype html>\n"
        "<html>\n"
        "<head>\n"
        "<title>%s</title>\n"
        "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
        "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
        "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n",
        title);
    if (refresh > 0)
        html_printf (client, "<meta http-equiv=\"refresh\" content=\"%d\">\n", refresh);
    html_printf (client,
        "<meta name=\"viewport\" content=\"width=device-width\">\n"
        "<style>\n"
        "body { font-family: sans-serif; margin: 0; }\n"
        "div.body { padding: 0px 10px 10px; }\n"
        "span.badge { background: #090; border-radius: 5px; color: #fff; padding: 5px 10px; }\n"
        "span.bar { box-shadow: 0px 1px 5px #333; font-size: 75%%; }\n"
        "table.form { border-collapse: collapse; margin-left: auto; margin-right: auto; margin-top: 10px; width: auto; }\n"
        "table.form td, table.form th { padding: 5px 2px; }\n"
        "table.form td.meter { border-right: solid 1px #ccc; padding: 0px; width: 400px; }\n"
        "table.form th { text-align: right; }\n"
        "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n"
        "table.striped tr:nth-child(even) { background: #fcfcfc; }\n"
        "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n"
        "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n"
        "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n"
        "table.nav { border-collapse: collapse; width: 100%%; }\n"
        "table.nav td { margin: 0; text-align: center; }\n"
        "td.nav a, td.nav a:active, td.nav a:hover, td.nav a:hover:link, td.nav a:hover:link:visited, td.nav a:link, td.nav a:link:visited, td.nav a:visited { background: inherit; color: inherit; font-size: 80%%; text-decoration: none; }\n"
        "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n"
        "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n"
        "td.nav:hover { background: #666; color: #fff; }\n"
        "td.nav:active { background: #000; color: #ff0; }\n"
        "</style>\n"
        "</head>\n"
        "<body>\n"
        "<table class=\"nav\"><tr>"
        "<td class=\"nav%s\"><a href=\"/\">Status</a></td>"
        "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
        "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
        "</tr></table>\n"
        "<div class=\"body\">\n",
        !strcmp (client->uri, "/") ? " sel" : "", !strcmp (client->uri, "/supplies") ? " sel" : "",
        !strcmp (client->uri, "/media") ? " sel" : "");
}

/*
 * 'html_printf()' - Send formatted text to the client, quoting as needed.
 */

static void html_printf (ippeve_client_t *client, /* I - Client */
    const char                           *format, /* I - Printf-style format string */
    ...)                                          /* I - Additional arguments as needed */
{
    va_list     ap;    /* Pointer to arguments */
    const char *start; /* Start of string */
    char        size,  /* Size character (h, l, L) */
        type;          /* Format type character */
    int width,         /* Width of field */
        prec;          /* Number of characters of precision */
    char tformat[100], /* Temporary format string for sprintf() */
        *tptr,         /* Pointer into temporary format */
        temp[1024];    /* Buffer for formatted numbers */
    char *s;           /* Pointer to string */

    /*
     * Loop through the format string, formatting as needed...
     */

    va_start (ap, format);
    start = format;

    while (*format) {
        if (*format == '%') {
            if (format > start)
                httpWrite2 (client->http, start, (size_t)(format - start));

            tptr = tformat;
            *tptr++ = *format++;

            if (*format == '%') {
                httpWrite2 (client->http, "%", 1);
                format++;
                start = format;
                continue;
            } else if (strchr (" -+#\'", *format))
                *tptr++ = *format++;

            if (*format == '*') {
                /*
                 * Get width from argument...
                 */

                format++;
                width = va_arg (ap, int);

                snprintf (tptr, sizeof (tformat) - (size_t)(tptr - tformat), "%d", width);
                tptr += strlen (tptr);
            } else {
                width = 0;

                while (isdigit (*format & 255)) {
                    if (tptr < (tformat + sizeof (tformat) - 1))
                        *tptr++ = *format;

                    width = width * 10 + *format++ - '0';
                }
            }

            if (*format == '.') {
                if (tptr < (tformat + sizeof (tformat) - 1))
                    *tptr++ = *format;

                format++;

                if (*format == '*') {
                    /*
                     * Get precision from argument...
                     */

                    format++;
                    prec = va_arg (ap, int);

                    snprintf (tptr, sizeof (tformat) - (size_t)(tptr - tformat), "%d", prec);
                    tptr += strlen (tptr);
                } else {
                    prec = 0;

                    while (isdigit (*format & 255)) {
                        if (tptr < (tformat + sizeof (tformat) - 1))
                            *tptr++ = *format;

                        prec = prec * 10 + *format++ - '0';
                    }
                }
            }

            if (*format == 'l' && format[1] == 'l') {
                size = 'L';

                if (tptr < (tformat + sizeof (tformat) - 2)) {
                    *tptr++ = 'l';
                    *tptr++ = 'l';
                }

                format += 2;
            } else if (*format == 'h' || *format == 'l' || *format == 'L') {
                if (tptr < (tformat + sizeof (tformat) - 1))
                    *tptr++ = *format;

                size = *format++;
            } else
                size = 0;

            if (!*format) {
                start = format;
                break;
            }

            if (tptr < (tformat + sizeof (tformat) - 1))
                *tptr++ = *format;

            type = *format++;
            *tptr = '\0';
            start = format;

            switch (type) {
            case 'E': /* Floating point formats */
            case 'G':
            case 'e':
            case 'f':
            case 'g':
                if ((size_t)(width + 2) > sizeof (temp))
                    break;

                snprintf (temp, sizeof (temp), tformat, va_arg (ap, double));

                httpWrite2 (client->http, temp, strlen (temp));
                break;

            case 'B': /* Integer formats */
            case 'X':
            case 'b':
            case 'd':
            case 'i':
            case 'o':
            case 'u':
            case 'x':
                if ((size_t)(width + 2) > sizeof (temp))
                    break;

#ifdef HAVE_LONG_LONG
                if (size == 'L')
                    snprintf (temp, sizeof (temp), tformat, va_arg (ap, long long));
                else
#endif /* HAVE_LONG_LONG */
                    if (size == 'l')
                        snprintf (temp, sizeof (temp), tformat, va_arg (ap, long));
                    else
                        snprintf (temp, sizeof (temp), tformat, va_arg (ap, int));

                httpWrite2 (client->http, temp, strlen (temp));
                break;

            case 'p': /* Pointer value */
                if ((size_t)(width + 2) > sizeof (temp))
                    break;

                snprintf (temp, sizeof (temp), tformat, va_arg (ap, void *));

                httpWrite2 (client->http, temp, strlen (temp));
                break;

            case 'c': /* Character or character array */
                if (width <= 1) {
                    temp[0] = (char)va_arg (ap, int);
                    temp[1] = '\0';
                    html_escape (client, temp, 1);
                } else
                    html_escape (client, va_arg (ap, char *), (size_t)width);
                break;

            case 's': /* String */
                if ((s = va_arg (ap, char *)) == NULL)
                    s = "(null)";

                html_escape (client, s, strlen (s));
                break;
            }
        } else
            format++;
    }

    if (format > start)
        httpWrite2 (client->http, start, (size_t)(format - start));

    va_end (ap);
}

/*
 * 'ipp_cancel_job()' - Cancel a job.
 */

static void ipp_cancel_job (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* Job information */

    /*
     * Get the job...
     */

    if ((job = find_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
        return;
    }

    /*
     * See if the job is already completed, canceled, or aborted; if so,
     * we can't cancel...
     */

    switch (job->state) {
    case IPP_JSTATE_CANCELED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already canceled - can\'t cancel.", job->id);
        break;

    case IPP_JSTATE_ABORTED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already aborted - can\'t cancel.", job->id);
        break;

    case IPP_JSTATE_COMPLETED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already completed - can\'t cancel.", job->id);
        break;

    default:
        /*
         * Cancel the job...
         */

        _cupsRWLockWrite (&(client->printer->rwlock));

        if (job->state == IPP_JSTATE_PROCESSING || (job->state == IPP_JSTATE_HELD && job->fd >= 0))
            job->cancel = 1;
        else {
            job->state = IPP_JSTATE_CANCELED;
            job->completed = time (NULL);
        }

        _cupsRWUnlock (&(client->printer->rwlock));

        respond_ipp (client, IPP_STATUS_OK, NULL);
        break;
    }
}

/*
 * 'ipp_cancel_my_jobs()' - Cancel all jobs.
 *
 * Note: Since ippeveprinter doesn't do spooling, this really just cancels the
 * current job.
 */

static void ipp_cancel_my_jobs (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* Job information */

    _cupsRWLockWrite (&client->printer->rwlock);

    if ((job = client->printer->active_job) != NULL) {
        /*
         * See if the job is already completed, canceled, or aborted; if so,
         * we can't cancel...
         */

        if (job->state < IPP_JSTATE_CANCELED) {
            /*
             * Cancel the job...
             */

            if (job->state == IPP_JSTATE_PROCESSING || (job->state == IPP_JSTATE_HELD && job->fd >= 0)) {
                job->cancel = 1;
            } else {
                job->state = IPP_JSTATE_CANCELED;
                job->completed = time (NULL);
            }
        }
    }

    respond_ipp (client, IPP_STATUS_OK, NULL);

    _cupsRWUnlock (&client->printer->rwlock);
}

/*
 * 'ipp_close_job()' - Close an open job.
 */

static void ipp_close_job (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* Job information */

    /*
     * Get the job...
     */

    if ((job = find_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
        return;
    }

    /*
     * See if the job is already completed, canceled, or aborted; if so,
     * we can't cancel...
     */

    switch (job->state) {
    case IPP_JSTATE_CANCELED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is canceled - can\'t close.", job->id);
        break;

    case IPP_JSTATE_ABORTED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is aborted - can\'t close.", job->id);
        break;

    case IPP_JSTATE_COMPLETED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is completed - can\'t close.", job->id);
        break;

    case IPP_JSTATE_PROCESSING:
    case IPP_JSTATE_STOPPED:
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job #%d is already closed.", job->id);
        break;

    default:
        respond_ipp (client, IPP_STATUS_OK, NULL);
        break;
    }
}

/*
 * 'ipp_create_job()' - Create a job object.
 */

static void ipp_create_job (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* New job */
    cups_array_t *ra;  /* Attributes to send in response */

    /*
     * Do we have a file to print?
     */

    if (have_document_data (client)) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Unexpected document data following request.");
        return;
    }

    /*
     * Validate print job attributes...
     */

    if (!valid_job_attributes (client))
        return;

    /*
     * Create the job...
     */

    if ((job = create_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_BUSY, "Currently printing another job.");
        return;
    }

    /*
     * Return the job info...
     */

    respond_ipp (client, IPP_STATUS_OK, NULL);

    ra = cupsArrayNew ((cups_array_func_t)strcmp, NULL);
    cupsArrayAdd (ra, "job-id");
    cupsArrayAdd (ra, "job-state");
    cupsArrayAdd (ra, "job-state-message");
    cupsArrayAdd (ra, "job-state-reasons");
    cupsArrayAdd (ra, "job-uri");

    copy_job_attributes (client, job, ra);
    cupsArrayDelete (ra);
}

/*
 * 'ipp_get_job_attributes()' - Get the attributes for a job object.
 */

static void ipp_get_job_attributes (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* Job */
    cups_array_t *ra;  /* requested-attributes */

    if ((job = find_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_NOT_FOUND, "Job not found.");
        return;
    }

    respond_ipp (client, IPP_STATUS_OK, NULL);

    ra = ippCreateRequestedArray (client->request);
    copy_job_attributes (client, job, ra);
    cupsArrayDelete (ra);
}

/*
 * 'ipp_get_jobs()' - Get a list of job objects.
 */

static void ipp_get_jobs (ippeve_client_t *client) /* I - Client */
{
    ipp_attribute_t *attr; /* Current attribute */
    const char      *which_jobs = NULL;
    /* which-jobs values */
    int              job_comparison; /* Job comparison */
    ipp_jstate_t     job_state;      /* job-state value */
    int              first_job_id,   /* First job ID */
        limit,                       /* Maximum number of jobs to return */
        count;                       /* Number of jobs that match */
    const char   *username;          /* Username */
    ippeve_job_t *job;               /* Current job pointer */
    cups_array_t *ra;                /* Requested attributes array */

    /*
     * See if the "which-jobs" attribute have been specified...
     */

    if ((attr = ippFindAttribute (client->request, "which-jobs", IPP_TAG_KEYWORD)) != NULL) {
        which_jobs = ippGetString (attr, 0, NULL);
        fprintf (stderr, "%s Get-Jobs which-jobs=%s", client->hostname, which_jobs);
    }

    if (!which_jobs || !strcmp (which_jobs, "not-completed")) {
        job_comparison = -1;
        job_state = IPP_JSTATE_STOPPED;
    } else if (!strcmp (which_jobs, "completed")) {
        job_comparison = 1;
        job_state = IPP_JSTATE_CANCELED;
    } else if (!strcmp (which_jobs, "aborted")) {
        job_comparison = 0;
        job_state = IPP_JSTATE_ABORTED;
    } else if (!strcmp (which_jobs, "all")) {
        job_comparison = 1;
        job_state = IPP_JSTATE_PENDING;
    } else if (!strcmp (which_jobs, "canceled")) {
        job_comparison = 0;
        job_state = IPP_JSTATE_CANCELED;
    } else if (!strcmp (which_jobs, "pending")) {
        job_comparison = 0;
        job_state = IPP_JSTATE_PENDING;
    } else if (!strcmp (which_jobs, "pending-held")) {
        job_comparison = 0;
        job_state = IPP_JSTATE_HELD;
    } else if (!strcmp (which_jobs, "processing")) {
        job_comparison = 0;
        job_state = IPP_JSTATE_PROCESSING;
    } else if (!strcmp (which_jobs, "processing-stopped")) {
        job_comparison = 0;
        job_state = IPP_JSTATE_STOPPED;
    } else {
        respond_ipp (
            client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "The which-jobs value \"%s\" is not supported.", which_jobs);
        ippAddString (client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, "which-jobs", NULL, which_jobs);
        return;
    }

    /*
     * See if they want to limit the number of jobs reported...
     */

    if ((attr = ippFindAttribute (client->request, "limit", IPP_TAG_INTEGER)) != NULL) {
        limit = ippGetInteger (attr, 0);

        fprintf (stderr, "%s Get-Jobs limit=%d", client->hostname, limit);
    } else
        limit = 0;

    if ((attr = ippFindAttribute (client->request, "first-job-id", IPP_TAG_INTEGER)) != NULL) {
        first_job_id = ippGetInteger (attr, 0);

        fprintf (stderr, "%s Get-Jobs first-job-id=%d", client->hostname, first_job_id);
    } else
        first_job_id = 1;

    /*
     * See if we only want to see jobs for a specific user...
     */

    username = NULL;

    if ((attr = ippFindAttribute (client->request, "my-jobs", IPP_TAG_BOOLEAN)) != NULL) {
        int my_jobs = ippGetBoolean (attr, 0);

        fprintf (stderr, "%s Get-Jobs my-jobs=%s\n", client->hostname, my_jobs ? "true" : "false");

        if (my_jobs) {
            if ((attr = ippFindAttribute (client->request, "requesting-user-name", IPP_TAG_NAME)) == NULL) {
                respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Need requesting-user-name with my-jobs.");
                return;
            }

            username = ippGetString (attr, 0, NULL);

            fprintf (stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n", client->hostname, username);
        }
    }

    /*
     * OK, build a list of jobs for this printer...
     */

    ra = ippCreateRequestedArray (client->request);

    respond_ipp (client, IPP_STATUS_OK, NULL);

    _cupsRWLockRead (&(client->printer->rwlock));

    for (count = 0, job = (ippeve_job_t *)cupsArrayFirst (client->printer->jobs); (limit <= 0 || count < limit) && job;
         job = (ippeve_job_t *)cupsArrayNext (client->printer->jobs)) {
        /*
         * Filter out jobs that don't match...
         */

        if ((job_comparison < 0 && job->state > job_state) || (job_comparison == 0 && job->state != job_state)
            || (job_comparison > 0 && job->state < job_state) || job->id < first_job_id
            || (username && job->username && strcasecmp (username, job->username)))
            continue;

        if (count > 0)
            ippAddSeparator (client->response);

        count++;
        copy_job_attributes (client, job, ra);
    }

    cupsArrayDelete (ra);

    _cupsRWUnlock (&(client->printer->rwlock));
}

/*
 * 'ipp_get_printer_attributes()' - Get the attributes for a printer object.
 */

static void ipp_get_printer_attributes (ippeve_client_t *client) /* I - Client */
{
    cups_array_t     *ra;      /* Requested attributes array */
    ippeve_printer_t *printer; /* Printer */

    /*
     * Send the attributes...
     */

    ra = ippCreateRequestedArray (client->request);
    printer = client->printer;

    respond_ipp (client, IPP_STATUS_OK, NULL);

    _cupsRWLockRead (&(printer->rwlock));

    copy_attributes (client->response, printer->attrs, ra, IPP_TAG_ZERO, IPP_TAG_CUPS_CONST);

    if (!ra || cupsArrayFind (ra, "printer-config-change-date-time"))
        ippAddDate (
            client->response, IPP_TAG_PRINTER, "printer-config-change-date-time", ippTimeToDate (printer->config_time));

    if (!ra || cupsArrayFind (ra, "printer-config-change-time"))
        ippAddInteger (client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-config-change-time",
            (int)(printer->config_time - printer->start_time));

    if (!ra || cupsArrayFind (ra, "printer-current-time"))
        ippAddDate (client->response, IPP_TAG_PRINTER, "printer-current-time", ippTimeToDate (time (NULL)));

    if (!ra || cupsArrayFind (ra, "printer-icons")) {
        char        uris[3][1024]; /* Buffers for URIs */
        const char *values[3];     /* Values for attribute */

        httpAssembleURI (HTTP_URI_CODING_ALL, uris[0], sizeof (uris[0]), WEB_SCHEME, NULL, client->host_field,
            client->host_port, "/icon-sm.png");
        httpAssembleURI (HTTP_URI_CODING_ALL, uris[1], sizeof (uris[1]), WEB_SCHEME, NULL, client->host_field,
            client->host_port, "/icon.png");
        httpAssembleURI (HTTP_URI_CODING_ALL, uris[2], sizeof (uris[2]), WEB_SCHEME, NULL, client->host_field,
            client->host_port, "/icon-lg.png");

        values[0] = uris[0];
        values[1] = uris[1];
        values[2] = uris[2];

        ippAddStrings (client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-icons", 3, NULL, values);
    }

    if (!ra || cupsArrayFind (ra, "printer-more-info")) {
        char uri[1024]; /* URI value */

        httpAssembleURI (
            HTTP_URI_CODING_ALL, uri, sizeof (uri), WEB_SCHEME, NULL, client->host_field, client->host_port, "/");
        ippAddString (client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-more-info", NULL, uri);
    }

    if (!ra || cupsArrayFind (ra, "printer-state"))
        ippAddInteger (client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", (int)printer->state);

    if (!ra || cupsArrayFind (ra, "printer-state-change-date-time"))
        ippAddDate (
            client->response, IPP_TAG_PRINTER, "printer-state-change-date-time", ippTimeToDate (printer->state_time));

    if (!ra || cupsArrayFind (ra, "printer-state-change-time"))
        ippAddInteger (client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-state-change-time",
            (int)(printer->state_time - printer->start_time));

    if (!ra || cupsArrayFind (ra, "printer-state-message")) {
        static const char *const messages[] = {"Idle.", "Printing.", "Stopped."};

        ippAddString (client->response, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-state-message", NULL,
            messages[printer->state - IPP_PSTATE_IDLE]);
    }

    if (!ra || cupsArrayFind (ra, "printer-state-reasons")) {
        if (printer->state_reasons == IPPEVE_PREASON_NONE) {
            ippAddString (client->response, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "printer-state-reasons",
                NULL, "none");
        } else {
            ipp_attribute_t *attr = NULL; /* printer-state-reasons */
            ippeve_preason_t bit;         /* Reason bit */
            int              i;           /* Looping var */
            char             reason[32];  /* Reason string */

            for (i = 0, bit = 1; i < (int)(sizeof (ippeve_preason_strings) / sizeof (ippeve_preason_strings[0]));
                 i++, bit *= 2) {
                if (printer->state_reasons & bit) {
                    snprintf (reason, sizeof (reason), "%s-%s", ippeve_preason_strings[i],
                        printer->state == IPP_PSTATE_IDLE         ? "report"
                        : printer->state == IPP_PSTATE_PROCESSING ? "warning"
                                                                  : "error");
                    if (attr)
                        ippSetString (client->response, &attr, ippGetCount (attr), reason);
                    else
                        attr = ippAddString (
                            client->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "printer-state-reasons", NULL, reason);
                }
            }
        }
    }

    if (!ra || cupsArrayFind (ra, "printer-strings-uri")) {
        char uri[1024]; /* URI value */

        httpAssembleURI (HTTP_URI_CODING_ALL, uri, sizeof (uri), WEB_SCHEME, NULL, client->host_field,
            client->host_port, "/en.strings");
        ippAddString (client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-strings-uri", NULL, uri);
    }

    if (!ra || cupsArrayFind (ra, "printer-supply-info-uri")) {
        char uri[1024]; /* URI value */

        httpAssembleURI (HTTP_URI_CODING_ALL, uri, sizeof (uri), WEB_SCHEME, NULL, client->host_field,
            client->host_port, "/supplies");
        ippAddString (client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-supply-info-uri", NULL, uri);
    }

    if (!ra || cupsArrayFind (ra, "printer-up-time"))
        ippAddInteger (client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-up-time",
            (int)(time (NULL) - printer->start_time));

    if (!ra || cupsArrayFind (ra, "printer-uri-supported")) {
        char        uris[2][1024];  /* Buffers for URIs */
        const char *values[2];      /* Values for attribute */
        int         num_values = 0; /* Number of values */

        httpAssembleURI (HTTP_URI_CODING_ALL, uris[0], sizeof (uris[0]), "ipp", NULL, client->host_field,
            client->host_port, "/ipp/print");
        values[num_values++] = uris[0];

#ifdef HAVE_TLS
        httpAssembleURI (HTTP_URI_CODING_ALL, uris[1], sizeof (uris[1]), "ipps", NULL, client->host_field,
            client->host_port, "/ipp/print");
        values[num_values++] = uris[1];
#endif /* HAVE_TLS */

        ippAddStrings (
            client->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", num_values, NULL, values);
    }

    if (!ra || cupsArrayFind (ra, "queued-job-count"))
        ippAddInteger (client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "queued-job-count",
            printer->active_job && printer->active_job->state < IPP_JSTATE_CANCELED);

    _cupsRWUnlock (&(printer->rwlock));

    cupsArrayDelete (ra);
}

/*
 * 'ipp_identify_printer()' - Beep or display a message.
 */

static void ipp_identify_printer (ippeve_client_t *client) /* I - Client */
{
    ipp_attribute_t *actions, /* identify-actions */
        *message;             /* message */

    actions = ippFindAttribute (client->request, "identify-actions", IPP_TAG_KEYWORD);
    message = ippFindAttribute (client->request, "message", IPP_TAG_TEXT);

    if (!actions || ippContainsString (actions, "sound")) {
#ifdef __APPLE__
        pid_t                    pid;         /* Process ID for "afplay" utility */
        static const char *const afplay[3] = {/* Arguments for "afplay" utility */
            "/usr/bin/afplay", "/System/Library/Sounds/Ping.aiff", NULL};

        posix_spawn (&pid, afplay[0], NULL, NULL, (char **)afplay, NULL);

#else
        putchar (0x07);
        fflush (stdout);
#endif /* __APPLE__ */
    }

    if (ippContainsString (actions, "display"))
        printf ("IDENTIFY from %s: %s\n", client->hostname,
            message ? ippGetString (message, 0, NULL) : "No message supplied");

    respond_ipp (client, IPP_STATUS_OK, NULL);
}

/*
 * 'ipp_print_job()' - Create a job object with an attached document.
 */

static void ipp_print_job (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* New job */

    /*
     * Validate print job attributes...
     */

    if (!valid_job_attributes (client)) {
        flush_document_data (client);
        return;
    }

    /*
     * Do we have a file to print?
     */

    if (!have_document_data (client)) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "No file in request.");
        return;
    }

    /*
     * Create the job...
     */

    if ((job = create_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_BUSY, "Currently printing another job.");
        return;
    }

    /*
     * Then finish getting the document data and process things...
     */

    finish_document_data (client, job);
}

/*
 * 'ipp_print_uri()' - Create a job object with a referenced document.
 */

static void ipp_print_uri (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t *job; /* New job */

    /*
     * Validate print job attributes...
     */

    if (!valid_job_attributes (client)) {
        flush_document_data (client);
        return;
    }

    /*
     * Create the job...
     */

    if ((job = create_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_BUSY, "Currently printing another job.");
        return;
    }

    /*
     * Then finish getting the document data and process things...
     */

    finish_document_uri (client, job);
}

/*
 * 'ipp_send_document()' - Add an attached document to a job object created with
 *                         Create-Job.
 */

static void ipp_send_document (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t    *job;       /* Job information */
    ipp_attribute_t *attr;      /* Current attribute */
    int              have_data; /* Have document data? */

    /*
     * Get the job...
     */

    if ((job = find_job (client)) == NULL) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
        return;
    }

    /*
     * See if we already have a document for this job or the job has already
     * in a terminating state...
     */

    have_data = have_document_data (client);

    if ((job->filename || job->fd >= 0) && have_data) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported.");
        return;
    } else if (job->state > IPP_JSTATE_HELD && have_data) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state.");
        return;
    }

    /*
     * Make sure we have the "last-document" operation attribute...
     */

    if ((attr = ippFindAttribute (client->request, "last-document", IPP_TAG_ZERO)) == NULL) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute.");
        return;
    } else if (ippGetGroupTag (attr) != IPP_TAG_OPERATION) {
        flush_document_data (client);
        respond_ipp (
            client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group.");
        return;
    } else if (ippGetValueTag (attr) != IPP_TAG_BOOLEAN || ippGetCount (attr) != 1) {
        flush_document_data (client);
        respond_unsupported (client, attr);
        return;
    }

    /*
     * Validate document attributes...
     */

    if (have_data && !valid_doc_attributes (client)) {
        flush_document_data (client);
        return;
    }

    if (!have_data && !job->filename)
        job->state = IPP_JSTATE_ABORTED;

    /*
     * Then finish getting the document data and process things...
     */

    _cupsRWLockWrite (&(client->printer->rwlock));

    copy_attributes (job->attrs, client->request, NULL, IPP_TAG_JOB, 0);

    if ((attr = ippFindAttribute (job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL)
        job->format = ippGetString (attr, 0, NULL);
    else if ((attr = ippFindAttribute (job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL)
        job->format = ippGetString (attr, 0, NULL);
    else
        job->format = "application/octet-stream";

    _cupsRWUnlock (&(client->printer->rwlock));

    if (have_data)
        finish_document_data (client, job);
}

/*
 * 'ipp_send_uri()' - Add a referenced document to a job object created with
 *                    Create-Job.
 */

static void ipp_send_uri (ippeve_client_t *client) /* I - Client */
{
    ippeve_job_t    *job;  /* Job information */
    ipp_attribute_t *attr; /* Current attribute */

    /*
     * Get the job...
     */

    if ((job = find_job (client)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
        return;
    }

    /*
     * See if we already have a document for this job or the job has already
     * in a non-terminating state...
     */

    if (job->filename || job->fd >= 0) {
        respond_ipp (client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported.");
        return;
    } else if (job->state > IPP_JSTATE_HELD) {
        flush_document_data (client);
        respond_ipp (client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state.");
        return;
    }

    if ((attr = ippFindAttribute (client->request, "last-document", IPP_TAG_ZERO)) == NULL) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute.");
        return;
    } else if (ippGetGroupTag (attr) != IPP_TAG_OPERATION) {
        respond_ipp (
            client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group.");
        return;
    } else if (ippGetValueTag (attr) != IPP_TAG_BOOLEAN || ippGetCount (attr) != 1) {
        respond_unsupported (client, attr);
        return;
    }

    /*
     * Validate document attributes...
     */

    if (!valid_doc_attributes (client)) {
        flush_document_data (client);
        return;
    }

    /*
     * Then finish getting the document data and process things...
     */

    _cupsRWLockWrite (&(client->printer->rwlock));

    copy_attributes (job->attrs, client->request, NULL, IPP_TAG_JOB, 0);

    if ((attr = ippFindAttribute (job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL)
        job->format = ippGetString (attr, 0, NULL);
    else if ((attr = ippFindAttribute (job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL)
        job->format = ippGetString (attr, 0, NULL);
    else
        job->format = "application/octet-stream";

    _cupsRWUnlock (&(client->printer->rwlock));

    finish_document_uri (client, job);
}

/*
 * 'ipp_validate_job()' - Validate job creation attributes.
 */

static void ipp_validate_job (ippeve_client_t *client) /* I - Client */
{
    if (valid_job_attributes (client))
        respond_ipp (client, IPP_STATUS_OK, NULL);
}

/*
 * 'ippserver_attr_cb()' - Determine whether an attribute should be loaded.
 */

static int                                 /* O - 1 to use, 0 to ignore */
ippserver_attr_cb (_ipp_file_t *f,         /* I - IPP file */
    void                       *user_data, /* I - User data pointer (unused) */
    const char                 *attr)                      /* I - Attribute name */
{
    int i,                                /* Current element */
        result;                           /* Result of comparison */
    static const char *const ignored[] = {/* Ignored attributes */
        "attributes-charset", "attributes-natural-language", "charset-configured", "charset-supported",
        "device-service-count", "device-uuid", "document-format-varying-attributes",
        "generated-natural-language-supported", "identify-actions-default", "identify-actions-supported",
        "ipp-features-supported", "ipp-versions-supproted", "ippget-event-life", "job-hold-until-supported",
        "job-hold-until-time-supported", "job-ids-supported", "job-k-octets-supported",
        "job-settable-attributes-supported", "multiple-document-jobs-supported", "multiple-operation-time-out",
        "multiple-operation-time-out-action", "natural-language-configured", "notify-attributes-supported",
        "notify-events-default", "notify-events-supported", "notify-lease-duration-default",
        "notify-lease-duration-supported", "notify-max-events-supported", "notify-pull-method-supported",
        "operations-supported", "printer-alert", "printer-alert-description", "printer-camera-image-uri",
        "printer-charge-info", "printer-charge-info-uri", "printer-config-change-date-time",
        "printer-config-change-time", "printer-current-time", "printer-detailed-status-messages", "printer-dns-sd-name",
        "printer-fax-log-uri", "printer-get-attributes-supported", "printer-icons", "printer-id", "printer-info",
        "printer-is-accepting-jobs", "printer-message-date-time", "printer-message-from-operator",
        "printer-message-time", "printer-more-info", "printer-service-type", "printer-settable-attributes-supported",
        "printer-state", "printer-state-message", "printer-state-reasons", "printer-static-resource-directory-uri",
        "printer-static-resource-k-octets-free", "printer-static-resource-k-octets-supported",
        "printer-strings-languages-supported", "printer-strings-uri", "printer-supply-info-uri", "printer-up-time",
        "printer-uri-supported", "printer-xri-supported", "queued-job-count", "reference-uri-scheme-supported",
        "uri-authentication-supported", "uri-security-supported", "which-jobs-supported",
        "xri-authentication-supported", "xri-security-supported", "xri-uri-scheme-supported"};

    (void)f;
    (void)user_data;

    for (i = 0, result = 1; i < (int)(sizeof (ignored) / sizeof (ignored[0])); i++) {
        if ((result = strcmp (attr, ignored[i])) <= 0)
            break;
    }

    return (result != 0);
}

/*
 * 'ippserver_error_cb()' - Log an error message.
 */

static int                                  /* O - 1 to continue, 0 to stop */
ippserver_error_cb (_ipp_file_t *f,         /* I - IPP file data */
    void                        *user_data, /* I - User data pointer (unused) */
    const char                  *error)                      /* I - Error message */
{
    (void)f;
    (void)user_data;

    _cupsLangPrintf (stderr, "%s\n", error);

    return (1);
}

/*
 * 'ippserver_token_cb()' - Process ippserver-specific config file tokens.
 */

static int                                  /* O - 1 to continue, 0 to stop */
ippserver_token_cb (_ipp_file_t *f,         /* I - IPP file data */
    _ipp_vars_t                 *vars,      /* I - IPP variables */
    void                        *user_data, /* I - User data pointer (unused) */
    const char                  *token)                      /* I - Current token */
{
    (void)vars;
    (void)user_data;

    if (!token) {
        /*
         * NULL token means do the initial setup - create an empty IPP message and
         * return...
         */

        f->attrs = ippNew ();
        f->group_tag = IPP_TAG_PRINTER;
    } else {
        _cupsLangPrintf (
            stderr, _ ("Unknown directive \"%s\" on line %d of \"%s\" ignored."), token, f->linenum, f->filename);
    }

    return (1);
}

/*
 * 'load_ippserver_attributes()' - Load IPP attributes from an ippserver file.
 */

static ipp_t *                                     /* O - IPP attributes or `NULL` on error */
load_ippserver_attributes (const char *servername, /* I - Server name or `NULL` for default */
    int                                serverport, /* I - Server port number */
    const char                        *filename,   /* I - ippserver attribute filename */
    cups_array_t                      *docformats)                      /* I - document-format-supported values */
{
    ipp_t      *attrs;     /* IPP attributes */
    _ipp_vars_t vars;      /* IPP variables */
    char        temp[256]; /* Temporary string */

    (void)docformats; /* for now */

    /*
     * Setup callbacks and variables for the printer configuration file...
     *
     * The following additional variables are supported:
     *
     * - SERVERNAME: The host name of the server.
     * - SERVERPORT: The default port of the server.
     */

    _ippVarsInit (&vars, (_ipp_fattr_cb_t)ippserver_attr_cb, (_ipp_ferror_cb_t)ippserver_error_cb,
        (_ipp_ftoken_cb_t)ippserver_token_cb);

    if (servername) {
        _ippVarsSet (&vars, "SERVERNAME", servername);
    } else {
        httpGetHostname (NULL, temp, sizeof (temp));
        _ippVarsSet (&vars, "SERVERNAME", temp);
    }

    snprintf (temp, sizeof (temp), "%d", serverport);
    _ippVarsSet (&vars, "SERVERPORT", temp);

    /*
     * Load attributes and values for the printer...
     */

    attrs = _ippFileParse (&vars, filename, NULL);

    /*
     * Free memory and return...
     */

    _ippVarsDeinit (&vars);

    return (attrs);
}

/*
 * 'load_legacy_attributes()' - Load IPP attributes using the old ippserver
 *                              options.
 */

static ipp_t *                                 /* O - IPP attributes or `NULL` on error */
load_legacy_attributes (const char *make,      /* I - Manufacturer name */
    const char                     *model,     /* I - Model name */
    int                             ppm,       /* I - pages-per-minute */
    int                             ppm_color, /* I - pages-per-minute-color */
    int                             duplex,    /* I - Duplex support? */
    cups_array_t                   *docformats)                  /* I - document-format-supported values */
{
    int    i;                           /* Looping var */
    ipp_t *attrs,                       /* IPP attributes */
        *col;                           /* Collection value */
    ipp_attribute_t *attr;              /* Current attribute */
    char             device_id[1024],   /* printer-device-id */
        *ptr,                           /* Pointer into device ID */
        make_model[128];                /* printer-make-and-model */
    const char *format,                 /* Current document format */
        *prefix;                        /* Prefix for device ID */
    int                      num_media; /* Number of media */
    const char *const       *media;     /* List of media */
    int                      num_ready; /* Number of loaded media */
    const char *const       *ready;     /* List of loaded media */
    pwg_media_t             *pwg;       /* PWG media size information */
    static const char *const media_supported[] = {
        /* media-supported values */
        "na_letter_8.5x11in",       /* Letter */
        "na_legal_8.5x14in",        /* Legal */
        "iso_a4_210x297mm",         /* A4 */
        "na_number-10_4.125x9.5in", /* #10 Envelope */
        "iso_dl_110x220mm"          /* DL Envelope */
    };
    static const char *const media_supported_color[] = {
        /* media-supported values */
        "na_letter_8.5x11in",       /* Letter */
        "na_legal_8.5x14in",        /* Legal */
        "iso_a4_210x297mm",         /* A4 */
        "na_number-10_4.125x9.5in", /* #10 Envelope */
        "iso_dl_110x220mm",         /* DL Envelope */
        "na_index-3x5_3x5in",       /* Photo 3x5 */
        "oe_photo-l_3.5x5in",       /* Photo L */
        "na_index-4x6_4x6in",       /* Photo 4x6 */
        "iso_a6_105x148mm",         /* A6 */
        "na_5x7_5x7in",             /* Photo 5x7 aka 2L */
        "iso_a5_148x210mm",         /* A5 */
    };
    static const char *const media_ready[] = {
        /* media-ready values */
        "na_letter_8.5x11in",      /* Letter */
        "na_number-10_4.125x9.5in" /* #10 */
    };
    static const char *const media_ready_color[] = {
        /* media-ready values */
        "na_letter_8.5x11in", /* Letter */
        "na_index-4x6_4x6in"  /* Photo 4x6 */
    };
    static const char *const media_source_supported[] = {
        /* media-source-supported values */
        "auto", "main", "manual", "by-pass-tray" /* AKA multi-purpose tray */
    };
    static const char *const media_source_supported_color[] = {/* media-source-supported values */
        "auto", "main", "photo"};
    static const char *const media_type_supported[] = {/* media-type-supported values */
        "auto", "cardstock", "envelope", "labels", "other", "stationery", "stationery-letterhead", "transparency"};
    static const char *const media_type_supported_color[] = {/* media-type-supported values */
        "auto", "cardstock", "envelope", "labels", "other", "stationery", "stationery-letterhead", "transparency",
        "photographic-glossy", "photographic-high-gloss", "photographic-matte", "photographic-satin",
        "photographic-semi-gloss"};
    static const int         media_bottom_margin_supported[] = {
        /* media-bottom-margin-supported values */
        635 /* 1/4" */
    };
    static const int media_bottom_margin_supported_color[] = {
        /* media-bottom/top-margin-supported values */
        0,   /* Borderless */
        1168 /* 0.46" (common HP inkjet bottom margin) */
    };
    static const int media_lr_margin_supported[] = {
        /* media-left/right-margin-supported values */
        340, /* 3.4mm (historical HP PCL A4 margin) */
        635  /* 1/4" */
    };
    static const int media_lr_margin_supported_color[] = {
        /* media-left/right-margin-supported values */
        0,   /* Borderless */
        340, /* 3.4mm (historical HP PCL A4 margin) */
        635  /* 1/4" */
    };
    static const int media_top_margin_supported[] = {
        /* media-top-margin-supported values */
        635 /* 1/4" */
    };
    static const int media_top_margin_supported_color[] = {
        /* media-top/top-margin-supported values */
        0,  /* Borderless */
        102 /* 0.04" (common HP inkjet top margin */
    };
    static const int         orientation_requested_supported[4] = {/* orientation-requested-supported values */
        IPP_ORIENT_PORTRAIT, IPP_ORIENT_LANDSCAPE, IPP_ORIENT_REVERSE_LANDSCAPE, IPP_ORIENT_REVERSE_PORTRAIT};
    static const char *const overrides_supported[] = {/* overrides-supported values */
        "document-numbers", "media", "media-col", "orientation-requested", "pages"};
    static const char *const print_color_mode_supported[] = {/* print-color-mode-supported values */
        "monochrome"};
    static const char *const print_color_mode_supported_color[] = {/* print-color-mode-supported values */
        "auto", "color", "monochrome"};
    static const int         print_quality_supported[] = {/* print-quality-supported values */
        IPP_QUALITY_DRAFT, IPP_QUALITY_NORMAL, IPP_QUALITY_HIGH};
    static const char *const printer_input_tray[] = {/* printer-input-tray values */
        "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto",
        "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=100;status=0;name=main",
        "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=manual",
        "type=sheetFeedAutoNonRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=by-pass-tray"};
    static const char *const printer_input_tray_color[] = {/* printer-input-tray values */
        "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto",
        "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=-2;status=0;name=main",
        "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=photo"};
    static const char *const printer_supply[] = {/* printer-supply values */
        "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
        "maxcapacity=100;level=25;colorantname=unknown;",
        "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
        "maxcapacity=100;level=75;colorantname=black;"};
    static const char *const printer_supply_color[] = {/* printer-supply values */
        "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;"
        "maxcapacity=100;level=25;colorantname=unknown;",
        "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=75;colorantname=black;",
        "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=50;colorantname=cyan;",
        "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=33;colorantname=magenta;",
        "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=67;colorantname=yellow;"};
    static const char *const printer_supply_description[] = {/* printer-supply-description values */
        "Toner Waste Tank", "Black Toner"};
    static const char *const printer_supply_description_color[] = {/* printer-supply-description values */
        "Ink Waste Tank", "Black Ink", "Cyan Ink", "Magenta Ink", "Yellow Ink"};
    static const int         pwg_raster_document_resolution_supported[] = {300, 600};
    static const char *const pwg_raster_document_type_supported[] = {"black_1", "sgray_8"};
    static const char *const pwg_raster_document_type_supported_color[] = {"black_1", "sgray_8", "srgb_8", "srgb_16"};
    static const char *const sides_supported[] = {/* sides-supported values */
        "one-sided", "two-sided-long-edge", "two-sided-short-edge"};
    static const char *const urf_supported[] = {/* urf-supported values */
        "CP1", "IS1-4-5-19", "MT1-2-3-4-5-6", "RS600", "V1.4", "W8"};
    static const char *const urf_supported_color[] = {/* urf-supported values */
        "CP1", "IS1-4-5-7-19", "MT1-2-3-4-5-6-8-9-10-11-12-13", "RS600", "SRGB24", "V1.4", "W8"};
    static const char *const urf_supported_color_duplex[] = {/* urf-supported values */
        "CP1", "IS1-4-5-7-19", "MT1-2-3-4-5-6-8-9-10-11-12-13", "RS600", "SRGB24", "V1.4", "W8", "DM3"};
    static const char *const urf_supported_duplex[] = {/* urf-supported values */
        "CP1", "IS1-4-5-19", "MT1-2-3-4-5-6", "RS600", "V1.4", "W8", "DM1"};

    attrs = ippNew ();

    if (ppm_color > 0) {
        num_media = (int)(sizeof (media_supported_color) / sizeof (media_supported_color[0]));
        media = media_supported_color;
        num_ready = (int)(sizeof (media_ready_color) / sizeof (media_ready_color[0]));
        ready = media_ready_color;
    } else {
        num_media = (int)(sizeof (media_supported) / sizeof (media_supported[0]));
        media = media_supported;
        num_ready = (int)(sizeof (media_ready) / sizeof (media_ready[0]));
        ready = media_ready;
    }

    /* color-supported */
    ippAddBoolean (attrs, IPP_TAG_PRINTER, "color-supported", ppm_color > 0);

    /* copies-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1);

    /* copies-supported */
    ippAddRange (attrs, IPP_TAG_PRINTER, "copies-supported", 1,
        (cupsArrayFind (docformats, (void *)"application/pdf") != NULL
            || cupsArrayFind (docformats, (void *)"image/jpeg") != NULL)
            ? 999
            : 1);

    /* document-password-supported */
    if (cupsArrayFind (docformats, (void *)"application/pdf"))
        ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 1023);

    /* finishing-template-supported */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "finishing-template-supported", NULL, "none");

    /* finishings-col-database */
    col = ippNew ();
    ippAddString (col, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "finishing-template", NULL, "none");
    ippAddCollection (attrs, IPP_TAG_PRINTER, "finishings-col-database", col);
    ippDelete (col);

    /* finishings-col-default */
    col = ippNew ();
    ippAddString (col, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "finishing-template", NULL, "none");
    ippAddCollection (attrs, IPP_TAG_PRINTER, "finishings-col-default", col);
    ippDelete (col);

    /* finishings-col-ready */
    col = ippNew ();
    ippAddString (col, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "finishing-template", NULL, "none");
    ippAddCollection (attrs, IPP_TAG_PRINTER, "finishings-col-ready", col);
    ippDelete (col);

    /* finishings-col-supported */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "finishings-col-supported", NULL,
        "finishing-template");

    /* finishings-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE);

    /* finishings-ready */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", IPP_FINISHINGS_NONE);

    /* finishings-supported */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", IPP_FINISHINGS_NONE);

    /* media-bottom-margin-supported */
    if (ppm_color > 0)
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported",
            (int)(sizeof (media_bottom_margin_supported) / sizeof (media_bottom_margin_supported[0])),
            media_bottom_margin_supported);
    else
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported",
            (int)(sizeof (media_bottom_margin_supported_color) / sizeof (media_bottom_margin_supported_color[0])),
            media_bottom_margin_supported_color);

    /* media-col-database and media-col-default */
    attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "media-col-database", num_media, NULL);
    for (i = 0; i < num_media; i++) {
        int bottom, left, /* media-xxx-margins */
            right, top;
        const char *source; /* media-source, if any */

        pwg = pwgMediaForPWG (media[i]);

        if (pwg->width < 21000 && pwg->length < 21000) {
            source = "photo"; /* Photo size media from photo tray */
            bottom =          /* Borderless margins */
                left = right = top = 0;
        } else if (pwg->width < 21000) {
            source = "by-pass-tray"; /* Envelopes from multi-purpose tray */
            bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
            left = /* Left/right margins are standard */
                right = media_lr_margin_supported[1];
            top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
        } else if (pwg->width == 21000) {
            source = NULL; /* A4 from any tray */
            bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
            left = /* Left/right margins are reduced */
                right = media_lr_margin_supported[0];
            top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
        } else {
            source = NULL; /* Other size media from any tray */
            bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
            left = /* Left/right margins are standard */
                right = media_lr_margin_supported[1];
            top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
        }

        col = create_media_col (media[i], source, NULL, pwg->width, pwg->length, bottom, left, right, top);
        ippSetCollection (attrs, &attr, i, col);

        ippDelete (col);
    }

    /* media-col-default */
    pwg = pwgMediaForPWG (ready[0]);

    if (pwg->width == 21000)
        col = create_media_col (ready[0], "main", "stationery", pwg->width, pwg->length,
            ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0],
            media_lr_margin_supported[0], media_lr_margin_supported[0],
            ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]);
    else
        col = create_media_col (ready[0], "main", "stationery", pwg->width, pwg->length,
            ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0],
            media_lr_margin_supported[1], media_lr_margin_supported[1],
            ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]);

    ippAddCollection (attrs, IPP_TAG_PRINTER, "media-col-default", col);

    ippDelete (col);

    /* media-col-ready */
    attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "media-col-ready", num_ready, NULL);
    for (i = 0; i < num_ready; i++) {
        int bottom, left, /* media-xxx-margins */
            right, top;
        const char *source, /* media-source */
            *type;          /* media-type */

        pwg = pwgMediaForPWG (ready[i]);

        if (pwg->width < 21000 && pwg->length < 21000) {
            source = "photo";             /* Photo size media from photo tray */
            type = "photographic-glossy"; /* Glossy photo paper */
            bottom =                      /* Borderless margins */
                left = right = top = 0;
        } else if (pwg->width < 21000) {
            source = "by-pass-tray"; /* Envelopes from multi-purpose tray */
            type = "envelope";       /* Envelope */
            bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
            left = /* Left/right margins are standard */
                right = media_lr_margin_supported[1];
            top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
        } else if (pwg->width == 21000) {
            source = "main";     /* A4 from main tray */
            type = "stationery"; /* Plain paper */
            bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
            left = /* Left/right margins are reduced */
                right = media_lr_margin_supported[0];
            top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
        } else {
            source = "main";     /* A4 from main tray */
            type = "stationery"; /* Plain paper */
            bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
            left = /* Left/right margins are standard */
                right = media_lr_margin_supported[1];
            top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
        }

        col = create_media_col (ready[i], source, type, pwg->width, pwg->length, bottom, left, right, top);
        ippSetCollection (attrs, &attr, i, col);
        ippDelete (col);
    }

    /* media-default */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-default", NULL, media[0]);

    /* media-left/right-margin-supported */
    if (ppm_color > 0) {
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported",
            (int)(sizeof (media_lr_margin_supported_color) / sizeof (media_lr_margin_supported_color[0])),
            media_lr_margin_supported_color);
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported",
            (int)(sizeof (media_lr_margin_supported_color) / sizeof (media_lr_margin_supported_color[0])),
            media_lr_margin_supported_color);
    } else {
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported",
            (int)(sizeof (media_lr_margin_supported) / sizeof (media_lr_margin_supported[0])),
            media_lr_margin_supported);
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported",
            (int)(sizeof (media_lr_margin_supported) / sizeof (media_lr_margin_supported[0])),
            media_lr_margin_supported);
    }

    /* media-ready */
    ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", num_ready, NULL, ready);

    /* media-supported */
    ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-supported", num_media, NULL, media);

    /* media-size-supported */
    attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "media-size-supported", num_media, NULL);
    for (i = 0; i < num_media; i++) {
        pwg = pwgMediaForPWG (media[i]);
        col = create_media_size (pwg->width, pwg->length);

        ippSetCollection (attrs, &attr, i, col);
        ippDelete (col);
    }

    /* media-source-supported */
    if (ppm_color > 0)
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-source-supported",
            (int)(sizeof (media_source_supported_color) / sizeof (media_source_supported_color[0])), NULL,
            media_source_supported_color);
    else
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-source-supported",
            (int)(sizeof (media_source_supported) / sizeof (media_source_supported[0])), NULL, media_source_supported);

    /* media-top-margin-supported */
    if (ppm_color > 0)
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported",
            (int)(sizeof (media_top_margin_supported) / sizeof (media_top_margin_supported[0])),
            media_top_margin_supported);
    else
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported",
            (int)(sizeof (media_top_margin_supported_color) / sizeof (media_top_margin_supported_color[0])),
            media_top_margin_supported_color);

    /* media-type-supported */
    if (ppm_color > 0)
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-type-supported",
            (int)(sizeof (media_type_supported_color) / sizeof (media_type_supported_color[0])), NULL,
            media_type_supported_color);
    else
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-type-supported",
            (int)(sizeof (media_type_supported) / sizeof (media_type_supported[0])), NULL, media_type_supported);

    /* orientation-requested-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT);

    /* orientation-requested-supported */
    if (cupsArrayFind (docformats, (void *)"application/pdf") || cupsArrayFind (docformats, (void *)"image/jpeg"))
        ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported",
            (int)(sizeof (orientation_requested_supported) / sizeof (orientation_requested_supported[0])),
            orientation_requested_supported);
    else
        ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", IPP_ORIENT_PORTRAIT);

    /* output-bin-default */
    if (ppm_color > 0)
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-up");
    else
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down");

    /* output-bin-supported */
    if (ppm_color > 0)
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-up");
    else
        ippAddString (
            attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down");

    /* overrides-supported */
    if (cupsArrayFind (docformats, (void *)"application/pdf"))
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "overrides-supported",
            (int)(sizeof (overrides_supported) / sizeof (overrides_supported[0])), NULL, overrides_supported);

    /* page-ranges-supported */
    ippAddBoolean (
        attrs, IPP_TAG_PRINTER, "page-ranges-supported", cupsArrayFind (docformats, (void *)"application/pdf") != NULL);

    /* pages-per-minute */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppm);

    /* pages-per-minute-color */
    if (ppm_color > 0)
        ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppm_color);

    /* print-color-mode-default */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-color-mode-default", NULL,
        ppm_color > 0 ? "auto" : "monochrome");

    /* print-color-mode-supported */
    if (ppm_color > 0)
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-color-mode-supported",
            (int)(sizeof (print_color_mode_supported_color) / sizeof (print_color_mode_supported_color[0])), NULL,
            print_color_mode_supported_color);
    else
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-color-mode-supported",
            (int)(sizeof (print_color_mode_supported) / sizeof (print_color_mode_supported[0])), NULL,
            print_color_mode_supported);

    /* print-content-optimize-default */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");

    /* print-content-optimize-supported */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");

    /* print-quality-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);

    /* print-quality-supported */
    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported",
        (int)(sizeof (print_quality_supported) / sizeof (print_quality_supported[0])), print_quality_supported);

    /* print-rendering-intent-default */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");

    /* print-rendering-intent-supported */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");

    /* printer-device-id */
    snprintf (device_id, sizeof (device_id), "MFG:%s;MDL:%s;", make, model);
    ptr = device_id + strlen (device_id);
    prefix = "CMD:";
    for (format = (const char *)cupsArrayFirst (docformats); format;
         format = (const char *)cupsArrayNext (docformats)) {
        if (!strcasecmp (format, "application/pdf"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sPDF", prefix);
        else if (!strcasecmp (format, "application/postscript"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sPS", prefix);
        else if (!strcasecmp (format, "application/vnd.hp-PCL"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sPCL", prefix);
        else if (!strcasecmp (format, "image/jpeg"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sJPEG", prefix);
        else if (!strcasecmp (format, "image/png"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sPNG", prefix);
        else if (!strcasecmp (format, "image/pwg-raster"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sPWG", prefix);
        else if (!strcasecmp (format, "image/urf"))
            snprintf (ptr, sizeof (device_id) - (size_t)(ptr - device_id), "%sURF", prefix);
        else
            continue;

        ptr += strlen (ptr);
        prefix = ",";
    }
    if (ptr < (device_id + sizeof (device_id) - 1)) {
        *ptr++ = ';';
        *ptr = '\0';
    }
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);

    /* printer-input-tray */
    if (ppm_color > 0) {
        attr = ippAddOctetString (attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray_color[0],
            (int)strlen (printer_input_tray_color[0]));
        for (i = 1; i < (int)(sizeof (printer_input_tray_color) / sizeof (printer_input_tray_color[0])); i++)
            ippSetOctetString (attrs, &attr, i, printer_input_tray_color[i], (int)strlen (printer_input_tray_color[i]));
    } else {
        attr = ippAddOctetString (
            attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray[0], (int)strlen (printer_input_tray[0]));
        for (i = 1; i < (int)(sizeof (printer_input_tray) / sizeof (printer_input_tray[0])); i++)
            ippSetOctetString (attrs, &attr, i, printer_input_tray[i], (int)strlen (printer_input_tray[i]));
    }

    /* printer-make-and-model */
    snprintf (make_model, sizeof (make_model), "%s %s", make, model);
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, make_model);

    /* printer-resolution-default */
    ippAddResolution (attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, 600, 600);

    /* printer-resolution-supported */
    ippAddResolution (attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600);

    /* printer-supply and printer-supply-description */
    if (ppm_color > 0) {
        attr = ippAddOctetString (
            attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], (int)strlen (printer_supply_color[0]));
        for (i = 1; i < (int)(sizeof (printer_supply_color) / sizeof (printer_supply_color[0])); i++)
            ippSetOctetString (attrs, &attr, i, printer_supply_color[i], (int)strlen (printer_supply_color[i]));

        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-supply-description",
            (int)(sizeof (printer_supply_description_color) / sizeof (printer_supply_description_color[0])), NULL,
            printer_supply_description_color);
    } else {
        attr = ippAddOctetString (
            attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], (int)strlen (printer_supply[0]));
        for (i = 1; i < (int)(sizeof (printer_supply) / sizeof (printer_supply[0])); i++)
            ippSetOctetString (attrs, &attr, i, printer_supply[i], (int)strlen (printer_supply[i]));

        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-supply-description",
            (int)(sizeof (printer_supply_description) / sizeof (printer_supply_description[0])), NULL,
            printer_supply_description);
    }

    /* pwg-raster-document-xxx-supported */
    if (cupsArrayFind (docformats, (void *)"image/pwg-raster")) {
        ippAddResolutions (attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported",
            (int)(sizeof (pwg_raster_document_resolution_supported)
                  / sizeof (pwg_raster_document_resolution_supported[0])),
            IPP_RES_PER_INCH, pwg_raster_document_resolution_supported, pwg_raster_document_resolution_supported);

        if (ppm_color > 0 && duplex)
            ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back",
                NULL, "rotated");
        else if (duplex)
            ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back",
                NULL, "normal");

        if (ppm_color > 0)
            ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
                "pwg-raster-document-type-supported",
                (int)(sizeof (pwg_raster_document_type_supported_color)
                      / sizeof (pwg_raster_document_type_supported_color[0])),
                NULL, pwg_raster_document_type_supported_color);
        else
            ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
                "pwg-raster-document-type-supported",
                (int)(sizeof (pwg_raster_document_type_supported) / sizeof (pwg_raster_document_type_supported[0])),
                NULL, pwg_raster_document_type_supported);
    }

    /* sides-default */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided");

    /* sides-supported */
    if (duplex)
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "sides-supported",
            (int)(sizeof (sides_supported) / sizeof (sides_supported[0])), NULL, sides_supported);
    else
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided");

    /* urf-supported */
    if (cupsArrayFind (docformats, (void *)"image/urf")) {
        if (ppm_color > 0) {
            if (duplex)
                ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported",
                    (int)(sizeof (urf_supported_color_duplex) / sizeof (urf_supported_color_duplex[0])), NULL,
                    urf_supported_color_duplex);
            else
                ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported",
                    (int)(sizeof (urf_supported_color) / sizeof (urf_supported_color[0])), NULL, urf_supported_color);
        } else if (duplex) {
            ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported",
                (int)(sizeof (urf_supported_duplex) / sizeof (urf_supported_duplex[0])), NULL, urf_supported_duplex);
        } else {
            ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported",
                (int)(sizeof (urf_supported) / sizeof (urf_supported[0])), NULL, urf_supported);
        }
    }

    return (attrs);
}

#if !CUPS_LITE
/*
 * 'load_ppd_attributes()' - Load IPP attributes from a PPD file.
 */

static ipp_t *                            /* O - IPP attributes or `NULL` on error */
load_ppd_attributes (const char *ppdfile, /* I - PPD filename */
    cups_array_t                *docformats)             /* I - document-format-supported values */
{
    int              i, j;                                        /* Looping vars */
    ipp_t           *attrs;                                       /* Attributes */
    ipp_attribute_t *attr;                                        /* Current attribute */
    ipp_t           *col;                                         /* Current collection value */
    ppd_file_t      *ppd;                                         /* PPD data */
    ppd_attr_t      *ppd_attr;                                    /* PPD attribute */
    ppd_choice_t    *ppd_choice;                                  /* PPD choice */
    ppd_size_t      *ppd_size;                                    /* Default PPD size */
    pwg_size_t      *pwg_size,                                    /* Current PWG size */
        *default_size = NULL;                                     /* Default PWG size */
    const char *default_source = "auto",                          /* Default media source */
        *default_type = NULL;                                     /* Default media type */
    pwg_map_t         *pwg_map;                                   /* Mapping from PWG to PPD keywords */
    _ppd_cache_t      *pc;                                        /* PPD cache */
    _pwg_finishings_t *finishings;                                /* Current finishings value */
    const char *template;                                         /* Current finishings-template value */
    int num_margins;                                              /* Number of media-xxx-margin-supported values */
    int margins[10];                                              /* media-xxx-margin-supported values */
    int xres,                                                     /* Default horizontal resolution */
        yres;                                                     /* Default vertical resolution */
    int                      num_urf;                             /* Number of urf-supported values */
    const char              *urf[10];                             /* urf-supported values */
    char                     urf_rs[32];                          /* RS value */
    static const int         orientation_requested_supported[] = {/* orientation-requested-supported values */
        IPP_ORIENT_PORTRAIT, IPP_ORIENT_LANDSCAPE, IPP_ORIENT_REVERSE_LANDSCAPE, IPP_ORIENT_REVERSE_PORTRAIT,
        IPP_ORIENT_NONE};
    static const char *const overrides_supported[] = {/* overrides-supported */
        "document-numbers", "media", "media-col", "orientation-requested", "pages"};
    static const char *const print_color_mode_supported[] = {/* print-color-mode-supported values */
        "auto", "monochrome", "auto-monochrome"};
    static const char *const print_color_mode_supported_color[] = {/* print-color-mode-supported values */
        "auto", "color", "monochrome"};
    static const int         print_quality_supported[] = {/* print-quality-supported values */
        IPP_QUALITY_DRAFT, IPP_QUALITY_NORMAL, IPP_QUALITY_HIGH};
    static const char *const printer_supply[] = {/* printer-supply values */
        "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
        "maxcapacity=100;level=25;colorantname=unknown;",
        "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
        "maxcapacity=100;level=75;colorantname=black;"};
    static const char *const printer_supply_color[] = {/* printer-supply values */
        "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;"
        "maxcapacity=100;level=25;colorantname=unknown;",
        "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=75;colorantname=black;",
        "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=50;colorantname=cyan;",
        "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=33;colorantname=magenta;",
        "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;"
        "maxcapacity=100;level=67;colorantname=yellow;"};
    static const char *const printer_supply_description[] = {/* printer-supply-description values */
        "Toner Waste Tank", "Black Toner"};
    static const char *const printer_supply_description_color[] = {/* printer-supply-description values */
        "Ink Waste Tank", "Black Ink", "Cyan Ink", "Magenta Ink", "Yellow Ink"};
    static const char *const pwg_raster_document_type_supported[] = {"black_1", "sgray_8"};
    static const char *const pwg_raster_document_type_supported_color[] = {"black_1", "sgray_8", "srgb_8", "srgb_16"};
    static const char *const sides_supported[] = {/* sides-supported values */
        "one-sided", "two-sided-long-edge", "two-sided-short-edge"};

    /*
     * Open the PPD file...
     */

    if ((ppd = ppdOpenFile (ppdfile)) == NULL) {
        ppd_status_t status; /* Load error */

        status = ppdLastError (&i);
        _cupsLangPrintf (
            stderr, _ ("ippeveprinter: Unable to open \"%s\": %s on line %d."), ppdfile, ppdErrorString (status), i);
        return (NULL);
    }

    ppdMarkDefaults (ppd);

    pc = _ppdCacheCreateWithPPD (ppd);

    if ((ppd_size = ppdPageSize (ppd, NULL)) != NULL) {
        /*
         * Look up default size...
         */

        for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i++, pwg_size++) {
            if (!strcmp (pwg_size->map.ppd, ppd_size->name)) {
                default_size = pwg_size;
                break;
            }
        }
    }

    if (!default_size) {
        /*
         * Default to A4 or Letter...
         */

        for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i++, pwg_size++) {
            if (!strcmp (pwg_size->map.ppd, "Letter") || !strcmp (pwg_size->map.ppd, "A4")) {
                default_size = pwg_size;
                break;
            }
        }

        if (!default_size)
            default_size = pc->sizes; /* Last resort: first size */
    }

    if ((ppd_choice = ppdFindMarkedChoice (ppd, "InputSlot")) != NULL)
        default_source = _ppdCacheGetSource (pc, ppd_choice->choice);

    if ((ppd_choice = ppdFindMarkedChoice (ppd, "MediaType")) != NULL)
        default_source = _ppdCacheGetType (pc, ppd_choice->choice);

    if ((ppd_attr = ppdFindAttr (ppd, "DefaultResolution", NULL)) != NULL) {
        /*
         * Use the PPD-defined default resolution...
         */

        if ((i = sscanf (ppd_attr->value, "%dx%d", &xres, &yres)) == 1)
            yres = xres;
        else if (i < 0)
            xres = yres = 300;
    } else {
        /*
         * Use default of 300dpi...
         */

        xres = yres = 300;
    }

    snprintf (urf_rs, sizeof (urf_rs), "RS%d", yres < xres ? yres : xres);

    num_urf = 0;
    urf[num_urf++] = "V1.5";
    urf[num_urf++] = "CP1";
    urf[num_urf++] = urf_rs;
    urf[num_urf++] = "W8";
    if (pc->sides_2sided_long)
        urf[num_urf++] = "DM1";
    if (ppd->color_device)
        urf[num_urf++] = "SRGB24";
    urf[num_urf++] = "PQ3-4-5";
    urf[num_urf++] = "IS1";
    urf[num_urf++] = "OB10";

    /*
     * PostScript printers accept PDF via one of the CUPS PDF to PostScript
     * filters, along with PostScript (of course) and JPEG...
     */

    cupsArrayAdd (docformats, "application/pdf");
    cupsArrayAdd (docformats, "application/postscript");
    cupsArrayAdd (docformats, "image/jpeg");
    cupsArrayAdd (docformats, "image/png");
    cupsArrayAdd (docformats, "application/octet-stream");

    /*
     * Create the attributes...
     */

    attrs = ippNew ();

    /* color-supported */
    ippAddBoolean (attrs, IPP_TAG_PRINTER, "color-supported", (char)ppd->color_device);

    /* copies-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1);

    /* copies-supported */
    ippAddRange (attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);

    /* document-password-supported */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127);

    /* finishing-template-supported */
    attr = ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template-supported",
        cupsArrayCount (pc->templates) + 1, NULL, NULL);
    ippSetString (attrs, &attr, 0, "none");
    for (i = 1, template = (const char *)cupsArrayFirst (pc->templates); template;
         i++, template = (const char *)cupsArrayNext (pc->templates))
        ippSetString (attrs, &attr, i, template);

    /* finishings-col-database */
    attr = ippAddCollections (
        attrs, IPP_TAG_PRINTER, "finishings-col-database", cupsArrayCount (pc->templates) + 1, NULL);

    col = ippNew ();
    ippAddString (col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
    ippSetCollection (attrs, &attr, 0, col);
    ippDelete (col);

    for (i = 1, template = (const char *)cupsArrayFirst (pc->templates); template;
         i++, template = (const char *)cupsArrayNext (pc->templates)) {
        col = ippNew ();
        ippAddString (col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
        ippSetCollection (attrs, &attr, i, col);
        ippDelete (col);
    }

    /* finishings-col-default */
    col = ippNew ();
    ippAddString (col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
    ippAddCollection (attrs, IPP_TAG_PRINTER, "finishings-col-default", col);
    ippDelete (col);

    /* finishings-col-ready */
    attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "finishings-col-ready", cupsArrayCount (pc->templates) + 1, NULL);

    col = ippNew ();
    ippAddString (col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
    ippSetCollection (attrs, &attr, 0, col);
    ippDelete (col);

    for (i = 1, template = (const char *)cupsArrayFirst (pc->templates); template;
         i++, template = (const char *)cupsArrayNext (pc->templates)) {
        col = ippNew ();
        ippAddString (col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
        ippSetCollection (attrs, &attr, i, col);
        ippDelete (col);
    }

    /* finishings-col-supported */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "finishings-col-supported", NULL,
        "finishing-template");

    /* finishings-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE);

    /* finishings-ready */
    attr = ippAddIntegers (
        attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", cupsArrayCount (pc->finishings) + 1, NULL);
    ippSetInteger (attrs, &attr, 0, IPP_FINISHINGS_NONE);
    for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst (pc->finishings); finishings;
         i++, finishings = (_pwg_finishings_t *)cupsArrayNext (pc->finishings))
        ippSetInteger (attrs, &attr, i, (int)finishings->value);

    /* finishings-supported */
    attr = ippAddIntegers (
        attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", cupsArrayCount (pc->finishings) + 1, NULL);
    ippSetInteger (attrs, &attr, 0, IPP_FINISHINGS_NONE);
    for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst (pc->finishings); finishings;
         i++, finishings = (_pwg_finishings_t *)cupsArrayNext (pc->finishings))
        ippSetInteger (attrs, &attr, i, (int)finishings->value);

    /* media-bottom-margin-supported */
    for (i = 0, num_margins = 0, pwg_size = pc->sizes;
         i < pc->num_sizes && num_margins < (int)(sizeof (margins) / sizeof (margins[0])); i++, pwg_size++) {
        for (j = 0; j < num_margins; j++) {
            if (margins[j] == pwg_size->bottom)
                break;
        }

        if (j >= num_margins)
            margins[num_margins++] = pwg_size->bottom;
    }
    for (i = 0; i < (num_margins - 1); i++) {
        for (j = i + 1; j < num_margins; j++) {
            if (margins[i] > margins[j]) {
                int mtemp = margins[i];

                margins[i] = margins[j];
                margins[j] = mtemp;
            }
        }
    }

    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", num_margins, margins);

    /* media-col-database */
    attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "media-col-database", pc->num_sizes, NULL);
    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i++, pwg_size++) {
        col = create_media_col (pwg_size->map.pwg, NULL, NULL, pwg_size->width, pwg_size->length, pwg_size->bottom,
            pwg_size->left, pwg_size->right, pwg_size->top);
        ippSetCollection (attrs, &attr, i, col);
        ippDelete (col);
    }
    /* media-col-default */
    col = create_media_col (default_size->map.pwg, default_source, default_type, default_size->width,
        default_size->length, default_size->bottom, default_size->left, default_size->right, default_size->top);
    ippAddCollection (attrs, IPP_TAG_PRINTER, "media-col-default", col);
    ippDelete (col);
#if 0
    {
        attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "media-col-ready", pc->num_sizes, NULL);
        for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes && i < 10; i++, pwg_size++) {
            col = create_media_col (pwg_size->map.pwg, default_source, default_type, pwg_size->width, pwg_size->length, pwg_size->bottom,
            pwg_size->left, pwg_size->right, pwg_size->top);
            ippSetCollection (attrs, &attr, i, col);
            ippDelete (col);
        }
    }
#else
    /* media-col-ready */
    col = create_media_col (default_size->map.pwg, default_source, default_type, default_size->width,
        default_size->length, default_size->bottom, default_size->left, default_size->right, default_size->top);

    ippAddCollection (attrs, IPP_TAG_PRINTER, "media-col-ready", col);
    ippDelete (col);
#endif

    /* media-default */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default", NULL, default_size->map.pwg);

    /* media-left-margin-supported */
    for (i = 0, num_margins = 0, pwg_size = pc->sizes;
         i < pc->num_sizes && num_margins < (int)(sizeof (margins) / sizeof (margins[0])); i++, pwg_size++) {
        for (j = 0; j < num_margins; j++) {
            if (margins[j] == pwg_size->left)
                break;
        }

        if (j >= num_margins)
            margins[num_margins++] = pwg_size->left;
    }
    for (i = 0; i < (num_margins - 1); i++) {
        for (j = i + 1; j < num_margins; j++) {
            if (margins[i] > margins[j]) {
                int mtemp = margins[i];

                margins[i] = margins[j];
                margins[j] = mtemp;
            }
        }
    }

    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", num_margins, margins);
#if 0
    {
        const char* pwgs[10] = {0};
        for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes && i < 10; i++, pwg_size++) {
            pwgs[i] = pwg_size->map.pwg;
        }
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready",pc->num_sizes,NULL,pwgs);
    }
#else
    /* media-ready */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, default_size->map.pwg);
#endif

    /* media-right-margin-supported */
    for (i = 0, num_margins = 0, pwg_size = pc->sizes;
         i < pc->num_sizes && num_margins < (int)(sizeof (margins) / sizeof (margins[0])); i++, pwg_size++) {
        for (j = 0; j < num_margins; j++) {
            if (margins[j] == pwg_size->right)
                break;
        }

        if (j >= num_margins)
            margins[num_margins++] = pwg_size->right;
    }
    for (i = 0; i < (num_margins - 1); i++) {
        for (j = i + 1; j < num_margins; j++) {
            if (margins[i] > margins[j]) {
                int mtemp = margins[i];

                margins[i] = margins[j];
                margins[j] = mtemp;
            }
        }
    }

    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", num_margins, margins);

    /* media-supported */
    attr = ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-supported", pc->num_sizes + 2, NULL, NULL);
    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i++, pwg_size++) {
        ippSetString (attrs, &attr, i, pwg_size->map.pwg);
    }
    ippSetString (attrs, &attr, i++, pc->custom_min_keyword);
    ippSetString (attrs, &attr, i++, pc->custom_max_keyword);
    /* media-size-supported */
    attr = ippAddCollections (attrs, IPP_TAG_PRINTER, "media-size-supported", pc->num_sizes + 1, NULL);

    for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i++, pwg_size++) {
        col = create_media_size (pwg_size->width, pwg_size->length);
        ippSetCollection (attrs, &attr, i, col);
        ippDelete (col);
    }
    {
        col = create_media_custom_size (
            pc->custom_min_width, pc->custom_max_width, pc->custom_min_length, pc->custom_max_length);
        ippSetCollection (attrs, &attr, i++, col);
        ippDelete (col);
    }
    /* media-source-supported */
    if (pc->num_sources > 0) {
        attr = ippAddStrings (
            attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source-supported", pc->num_sources, NULL, NULL);
        for (i = 0, pwg_map = pc->sources; i < pc->num_sources; i++, pwg_map++)
            ippSetString (attrs, &attr, i, pwg_map->pwg);
    } else {
#if 0
        attr = ippAddStrings (
                              attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source-supported", pc->num_sizes, NULL, NULL);

        for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i++, pwg_size++) {
            ippSetString (attrs, &attr, i, "auto");
        }

#else
        const char *media_sources[] = {"auto"};
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-source-supported",
            sizeof (media_sources) / sizeof (media_sources[0]), NULL, media_sources);
#endif
    }
    /* media-top-margin-supported */
    for (i = 0, num_margins = 0, pwg_size = pc->sizes;
         i < pc->num_sizes && num_margins < (int)(sizeof (margins) / sizeof (margins[0])); i++, pwg_size++) {
        for (j = 0; j < num_margins; j++) {
            if (margins[j] == pwg_size->top)
                break;
        }

        if (j >= num_margins)
            margins[num_margins++] = pwg_size->top;
    }
    for (i = 0; i < (num_margins - 1); i++) {
        for (j = i + 1; j < num_margins; j++) {
            if (margins[i] > margins[j]) {
                int mtemp = margins[i];

                margins[i] = margins[j];
                margins[j] = mtemp;
            }
        }
    }

    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", num_margins, margins);

    /* media-type-supported */
    if (pc->num_types > 0) {
        attr = ippAddStrings (
            attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type-supported", pc->num_types, NULL, NULL);
        for (i = 0, pwg_map = pc->types; i < pc->num_types; i++, pwg_map++)
            ippSetString (attrs, &attr, i, pwg_map->pwg);
    } else {
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "media-type-supported", NULL, "auto");
    }

    /* orientation-requested-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT);

    /* orientation-requested-supported */
    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported",
        (int)(sizeof (orientation_requested_supported) / sizeof (orientation_requested_supported[0])),
        orientation_requested_supported);

    /* output-bin-default */
    if (pc->num_bins > 0)
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, pc->bins->pwg);
    else
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down");

    /* output-bin-supported */
    if (pc->num_bins > 0) {
        attr
            = ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-supported", pc->num_bins, NULL, NULL);
        for (i = 0, pwg_map = pc->bins; i < pc->num_bins; i++, pwg_map++)
            ippSetString (attrs, &attr, i, pwg_map->pwg);
    } else {
#if 0
        attr
            = ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-supported", pc->num_sizes, NULL, NULL);
            for (i = 0;i < pc->num_sizes; i++) {
                ippSetString (attrs, &attr, i, "face-down");
            }
#else
        ippAddString (
            attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down");
#endif
    }

    /* overrides-supported */
    ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "overrides-supported",
        (int)(sizeof (overrides_supported) / sizeof (overrides_supported[0])), NULL, overrides_supported);

    /* page-ranges-supported */
    ippAddBoolean (attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1);

    /* pages-per-minute */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppd->throughput);

    /* pages-per-minute-color */
    if (ppd->color_device)
        ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppd->throughput);

    /* print-color-mode-default */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-color-mode-default", NULL,
        ppd->color_device ? "auto" : "monochrome");

    /* print-color-mode-supported */
    if (ppd->color_device)
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-color-mode-supported",
            (int)(sizeof (print_color_mode_supported_color) / sizeof (print_color_mode_supported_color[0])), NULL,
            print_color_mode_supported_color);
    else
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-color-mode-supported",
            (int)(sizeof (print_color_mode_supported) / sizeof (print_color_mode_supported[0])), NULL,
            print_color_mode_supported);

    /* print-content-optimize-default */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");

    /* print-content-optimize-supported */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");

    /* print-quality-default */
    ippAddInteger (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);

    /* print-quality-supported */
    ippAddIntegers (attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported",
        (int)(sizeof (print_quality_supported) / sizeof (print_quality_supported[0])), print_quality_supported);

    /* print-rendering-intent-default */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");

    /* print-rendering-intent-supported */
    ippAddString (
        attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");

    /* printer-device-id */
    if ((ppd_attr = ppdFindAttr (ppd, "1284DeviceId", NULL)) != NULL) {
        /*
         * Use the device ID string from the PPD...
         */

        ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, ppd_attr->value);
    } else {
        /*
         * Synthesize a device ID string...
         */

        char device_id[1024]; /* Device ID string */

        snprintf (device_id, sizeof (device_id), "MFG:%s;MDL:%s;CMD:PS;", ppd->manufacturer, ppd->modelname);

        ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);
    }

    /* printer-input-tray */
    if (pc->num_sources > 0) {
        for (i = 0, attr = NULL; i < pc->num_sources; i++) {
            char input_tray[1024]; /* printer-input-tray value */
            if (!strcmp (pc->sources[i].pwg, "manual") || strstr (pc->sources[i].pwg, "-man") != NULL)
                snprintf (input_tray, sizeof (input_tray),
                    "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=%s",
                    pc->sources[i].pwg);
            else
                snprintf (input_tray, sizeof (input_tray),
                    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s",
                    pc->sources[i].pwg);
            if (attr)
                ippSetOctetString (attrs, &attr, i, input_tray, (int)strlen (input_tray));
            else
                attr = ippAddOctetString (
                    attrs, IPP_TAG_PRINTER, "printer-input-tray", input_tray, (int)strlen (input_tray));
        }

    } else {
        //  char	input_tray[1024];	/* printer-input-tray value */
#if 0
        {

            attr = NULL;
            for (i = 0, pwg_size = pc->sizes;i < pc->num_sizes; i++, pwg_size++) {
                char input_tray[1024]; /* printer-input-tray value */

                snprintf (input_tray, sizeof (input_tray),
                    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s",
                          pwg_size->map.pwg);
                if (attr)
                    ippSetOctetString (attrs, &attr, i, input_tray, (int)strlen (input_tray));
                else
                    attr = ippAddOctetString (attrs, IPP_TAG_PRINTER, "printer-input-tray", input_tray, (int)strlen (input_tray));
            }
        }
#else
        static const char *printer_input_tray
            = "type=other;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto";
        attr = ippAddOctetString (
            attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray, (int)strlen (printer_input_tray));
#endif
        /* snprintf(input_tray, sizeof(input_tray),
         * "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=manual"); */
        /* ippSetOctetString(attrs, &attr, 1, input_tray, (int)strlen(input_tray)); */
    }
    {
#if 0
            attr = NULL;
            for (i = 0, pwg_size = pc->sizes;i < pc->num_sizes; i++, pwg_size++) {
                static const char *output_tray
                    = "type=other;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;remaining=0;stackingorder=0;pagedelivery=0;name=auto";
                if (attr)
                    ippSetOctetString (attrs, &attr, i, output_tray, (int)strlen (output_tray));
                else
                    attr = ippAddOctetString (attrs, IPP_TAG_PRINTER, "printer-output-tray", output_tray, (int)strlen (output_tray));
            }
#else
            static const char *printer_output_tray
                = "type=other;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;remaining=0;stackingorder=0;pagedelivery=0;name=auto";

            ippAddOctetString (attrs, IPP_TAG_PRINTER, "printer-output-tray", printer_output_tray,
                (int)strlen (printer_output_tray));
#endif
        }
    /* printer-make-and-model */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, ppd->nickname);

    /* printer-resolution-default */
    ippAddResolution (attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xres, yres);

    /* printer-resolution-supported */
    ippAddResolution (attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, xres, yres);

    /* printer-supply and printer-supply-description */
    if (ppd->color_device) {
        attr = ippAddOctetString (
            attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], (int)strlen (printer_supply_color[0]));
        for (i = 1; i < (int)(sizeof (printer_supply_color) / sizeof (printer_supply_color[0])); i++)
            ippSetOctetString (attrs, &attr, i, printer_supply_color[i], (int)strlen (printer_supply_color[i]));

        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-supply-description",
            (int)(sizeof (printer_supply_description_color) / sizeof (printer_supply_description_color[0])), NULL,
            printer_supply_description_color);
    } else {
        attr = ippAddOctetString (
            attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], (int)strlen (printer_supply[0]));
        for (i = 1; i < (int)(sizeof (printer_supply) / sizeof (printer_supply[0])); i++)
            ippSetOctetString (attrs, &attr, i, printer_supply[i], (int)strlen (printer_supply[i]));

        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_TEXT), "printer-supply-description",
            (int)(sizeof (printer_supply_description) / sizeof (printer_supply_description[0])), NULL,
            printer_supply_description);
    }

    /* pwg-raster-document-xxx-supported */
    if (cupsArrayFind (docformats, (void *)"image/pwg-raster")) {
        ippAddResolution (
            attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xres, yres);

        if (pc->sides_2sided_long)
            ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back",
                NULL, "normal");

        if (ppd->color_device)
            ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
                "pwg-raster-document-type-supported",
                (int)(sizeof (pwg_raster_document_type_supported_color)
                      / sizeof (pwg_raster_document_type_supported_color[0])),
                NULL, pwg_raster_document_type_supported_color);
        else
            ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD),
                "pwg-raster-document-type-supported",
                (int)(sizeof (pwg_raster_document_type_supported) / sizeof (pwg_raster_document_type_supported[0])),
                NULL, pwg_raster_document_type_supported);
    }

    /* sides-default */
    ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided");

    /* sides-supported */
    if (pc->sides_2sided_long)
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "sides-supported",
            (int)(sizeof (sides_supported) / sizeof (sides_supported[0])), NULL, sides_supported);
    else
        ippAddString (attrs, IPP_TAG_PRINTER, IPP_CONST_TAG (IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided");

    /* urf-supported */
    if (cupsArrayFind (docformats, (void *)"image/urf"))
        ippAddStrings (attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", num_urf, NULL, urf);

    /*
     * Free the PPD file and return the attributes...
     */

    _ppdCacheDestroy (pc);

    ppdClose (ppd);

    return (attrs);
}
#endif /* !CUPS_LITE */

#if HAVE_LIBPAM
/*
 * 'pam_func()' - PAM conversation function.
 */

static int                              /* O - Success or failure */
pam_func (int                  num_msg, /* I - Number of messages */
    const struct pam_message **msg,     /* I - Messages */
    struct pam_response      **resp,    /* O - Responses */
    void                      *appdata_ptr)
/* I - Pointer to connection */
{
    int                  i;       /* Looping var */
    struct pam_response *replies; /* Replies */
    ippeve_authdata_t   *data;    /* Pointer to auth data */

    /*
     * Allocate memory for the responses...
     */

    if ((replies = malloc (sizeof (struct pam_response) * (size_t)num_msg)) == NULL)
        return (PAM_CONV_ERR);

    /*
     * Answer all of the messages...
     */

    data = (ippeve_authdata_t *)appdata_ptr;

    for (i = 0; i < num_msg; i++) {
        switch (msg[i]->msg_style) {
        case PAM_PROMPT_ECHO_ON:
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp = strdup (data->username);
            break;

        case PAM_PROMPT_ECHO_OFF:
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp = strdup (data->password);
            break;

        case PAM_TEXT_INFO:
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp = NULL;
            break;

        case PAM_ERROR_MSG:
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp = NULL;
            break;

        default:
            free (replies);
            return (PAM_CONV_ERR);
        }
    }

    /*
     * Return the responses back to PAM...
     */

    *resp = replies;

    return (PAM_SUCCESS);
}
#endif /* HAVE_LIBPAM */

/*
 * 'parse_options()' - Parse URL options into CUPS options.
 *
 * The client->options string is destroyed by this function.
 */

static int                              /* O - Number of options */
parse_options (ippeve_client_t *client, /* I - Client */
    cups_option_t             **options)            /* O - Options */
{
    char *name,          /* Name */
        *value,          /* Value */
        *next;           /* Next name=value pair */
    int num_options = 0; /* Number of options */

    *options = NULL;

    for (name = client->options; name && *name; name = next) {
        printf ("options name: %s\n", name);
        if ((value = strchr (name, '=')) == NULL)
            break;

        *value++ = '\0';
        if ((next = strchr (value, '&')) != NULL)
            *next++ = '\0';

        num_options = cupsAddOption (name, value, num_options, options);
    }

    return (num_options);
}

/*
 * 'process_attr_message()' - Process an ATTR: message from a command.
 */

static void process_attr_message (ippeve_job_t *job, /* I - Job */
    char                                       *message)                                   /* I - Message */
{
    int i,                         /* Looping var */
        num_options = 0;           /* Number of name=value pairs */
    cups_option_t *options = NULL, /* name=value pairs from message */
        *option;                   /* Current option */
    ipp_attribute_t *attr;         /* Current attribute */

    /*
     * Grab attributes from the message line...
     */

    num_options = cupsParseOptions (message + 5, num_options, &options);

    /*
     * Loop through the options and record them in the printer or job objects...
     */

    for (i = num_options, option = options; i > 0; i--, option++) {
        if (!strcmp (option->name, "job-impressions")) {
            /*
             * Update job-impressions attribute...
             */

            job->impressions = atoi (option->value);
        } else if (!strcmp (option->name, "job-impressions-completed")) {
            /*
             * Update job-impressions-completed attribute...
             */

            job->impcompleted = atoi (option->value);
        } else if (!strncmp (option->name, "marker-", 7) || !strcmp (option->name, "printer-alert")
                   || !strcmp (option->name, "printer-alert-description") || !strcmp (option->name, "printer-supply")
                   || !strcmp (option->name, "printer-supply-description")) {
            /*
             * Update Printer Status attribute...
             */

            _cupsRWLockWrite (&job->printer->rwlock);

            if ((attr = ippFindAttribute (job->printer->attrs, option->name, IPP_TAG_ZERO)) != NULL)
                ippDeleteAttribute (job->printer->attrs, attr);

            cupsEncodeOption (job->printer->attrs, IPP_TAG_PRINTER, option->name, option->value);

            _cupsRWUnlock (&job->printer->rwlock);
        } else {
            /*
             * Something else that isn't currently supported...
             */

            fprintf (stderr, "[Job %d] Ignoring update of attribute \"%s\" with value \"%s\".\n", job->id, option->name,
                option->value);
        }
    }

    cupsFreeOptions (num_options, options);
}

/*
 * 'process_client()' - Process client requests on a thread.
 */

static void *                            /* O - Exit status */
process_client (ippeve_client_t *client) /* I - Client */
{
    /*
     * Loop until we are out of requests or timeout (30 seconds)...
     */

#ifdef HAVE_TLS
    int first_time = 1; /* First time request? */
#endif                  /* HAVE_TLS */

    while (httpWait (client->http, 30000)) {
#ifdef HAVE_TLS
        if (first_time) {
            /*
             * See if we need to negotiate a TLS connection...
             */

            char buf[1]; /* First byte from client */

            if (recv (httpGetFd (client->http), buf, 1, MSG_PEEK) == 1 && (!buf[0] || !strchr ("DGHOPT", buf[0]))) {
                fprintf (stderr, "%s Starting HTTPS session.\n", client->hostname);

                if (httpEncryption (client->http, HTTP_ENCRYPTION_ALWAYS)) {
                    fprintf (stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString ());
                    break;
                }

                fprintf (stderr, "%s Connection now encrypted.\n", client->hostname);
            }

            first_time = 0;
        }
#endif /* HAVE_TLS */

        if (!process_http (client))
            break;
    }

    /*
     * Close the connection to the client and return...
     */

    delete_client (client);

    return (NULL);
}

/*
 * 'process_http()' - Process a HTTP request.
 */

int                                    /* O - 1 on success, 0 on failure */
process_http (ippeve_client_t *client) /* I - Client connection */
{
    char          uri[1024];   /* URI */
    http_state_t  http_state;  /* HTTP state */
    http_status_t http_status; /* HTTP status */
    ipp_state_t   ipp_state;   /* State of IPP transfer */
    char          scheme[32],  /* Method/scheme */
        userpass[128],         /* Username:password */
        hostname[HTTP_MAX_HOST],
        /* Hostname */
        *ptr;                                 /* Pointer into value */
    int                      port;            /* Port number */
    static const char *const http_states[] = {/* Strings for logging HTTP method */
        "WAITING", "OPTIONS", "GET", "GET_SEND", "HEAD", "POST", "POST_RECV", "POST_SEND", "PUT", "PUT_RECV", "DELETE",
        "TRACE", "CONNECT", "STATUS", "UNKNOWN_METHOD", "UNKNOWN_VERSION"};

    /*
     * Clear state variables...
     */

    client->username[0] = '\0';

    ippDelete (client->request);
    ippDelete (client->response);

    client->request = NULL;
    client->response = NULL;
    client->operation = HTTP_STATE_WAITING;

    /*
     * Read a request from the connection...
     */

    while ((http_state = httpReadRequest (client->http, uri, sizeof (uri))) == HTTP_STATE_WAITING)
        usleep (1);

    /*
     * Parse the request line...
     */

    if (http_state == HTTP_STATE_ERROR) {
        if (httpError (client->http) == EPIPE)
            fprintf (stderr, "%s Client closed connection.\n", client->hostname);
        else
            fprintf (stderr, "%s Bad request line (%s).\n", client->hostname, strerror (httpError (client->http)));

        return (0);
    } else if (http_state == HTTP_STATE_UNKNOWN_METHOD) {
        fprintf (stderr, "%s Bad/unknown operation.\n", client->hostname);
        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    } else if (http_state == HTTP_STATE_UNKNOWN_VERSION) {
        fprintf (stderr, "%s Bad HTTP version.\n", client->hostname);
        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    }

    fprintf (stderr, "%s %s %s\n", client->hostname, http_states[http_state], uri);

    /*
     * Separate the URI into its components...
     */

    if (httpSeparateURI (HTTP_URI_CODING_MOST, uri, scheme, sizeof (scheme), userpass, sizeof (userpass), hostname,
            sizeof (hostname), &port, client->uri, sizeof (client->uri))
            < HTTP_URI_STATUS_OK
        && (http_state != HTTP_STATE_OPTIONS || strcmp (uri, "*"))) {
        fprintf (stderr, "%s Bad URI \"%s\".\n", client->hostname, uri);
        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    }

    if ((client->options = strchr (client->uri, '?')) != NULL)
        *(client->options)++ = '\0';

    /*
     * Process the request...
     */

    client->start = time (NULL);
    client->operation = httpGetState (client->http);

    /*
     * Parse incoming parameters until the status changes...
     */

    while ((http_status = httpUpdate (client->http)) == HTTP_STATUS_CONTINUE)
        ;

    if (http_status != HTTP_STATUS_OK) {
        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    }

    /*
     * Validate the host header...
     */

    if (!httpGetField (client->http, HTTP_FIELD_HOST)[0] && httpGetVersion (client->http) >= HTTP_VERSION_1_1) {
        /*
         * HTTP/1.1 and higher require the "Host:" field...
         */

        fprintf (stderr, "%s Missing Host: header.\n", client->hostname);
        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    }

    strlcpy (client->host_field, httpGetField (client->http, HTTP_FIELD_HOST), sizeof (client->host_field));
    if ((ptr = strrchr (client->host_field, ':')) != NULL) {
        /*
         * Grab port number from Host: header...
         */

        *ptr++ = '\0';
        client->host_port = atoi (ptr);
    } else {
        /*
         * Use the default port number...
         */

        client->host_port = client->printer->port;
    }

    if ((ptr = strstr (client->host_field, ".local")) == NULL)
        ptr = strrchr (client->host_field, '.');

    if (!isdigit (client->host_field[0] & 255) && client->host_field[0] != '['
        && strcmp (client->host_field, client->printer->hostname) && strcmp (client->host_field, "localhost")
        && (!ptr || (strcmp (ptr, ".local") && strcmp (ptr, ".local.")))) {
        fprintf (stderr, "%s Bad Host: header '%s'.\n", client->hostname, client->host_field);
        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    }

    /*
     * Handle HTTP Upgrade...
     */

    if (!strcasecmp (httpGetField (client->http, HTTP_FIELD_CONNECTION), "Upgrade")) {
#ifdef HAVE_TLS
        if (strstr (httpGetField (client->http, HTTP_FIELD_UPGRADE), "TLS/") != NULL
            && !httpIsEncrypted (client->http)) {
            if (!respond_http (client, HTTP_STATUS_SWITCHING_PROTOCOLS, NULL, NULL, 0))
                return (0);

            fprintf (stderr, "%s Upgrading to encrypted connection.\n", client->hostname);

            if (httpEncryption (client->http, HTTP_ENCRYPTION_REQUIRED)) {
                fprintf (stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString ());
                return (0);
            }

            fprintf (stderr, "%s Connection now encrypted.\n", client->hostname);
        } else
#endif /* HAVE_TLS */

            if (!respond_http (client, HTTP_STATUS_NOT_IMPLEMENTED, NULL, NULL, 0))
                return (0);
    }

    /*
     * Handle new transfers...
     */
    switch (client->operation) {
    case HTTP_STATE_OPTIONS:
        /*
         * Do OPTIONS command...
         */

        return (respond_http (client, HTTP_STATUS_OK, NULL, NULL, 0));

    case HTTP_STATE_HEAD:
        if (!strcmp (client->uri, "/en.strings"))
            return (respond_http (client, HTTP_STATUS_OK, NULL, "text/strings", 0));
        else if (!strcmp (client->uri, "/icon.png") || !strcmp (client->uri, "/icon-lg.png")
                 || !strcmp (client->uri, "/icon-sm.png"))
            return (respond_http (client, HTTP_STATUS_OK, NULL, "image/png", 0));
        else if (!strcmp (client->uri, "/") || !strcmp (client->uri, "/media") || !strcmp (client->uri, "/supplies"))
            return (respond_http (client, HTTP_STATUS_OK, NULL, "text/html", 0));
        else
            return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));

    case HTTP_STATE_GET:
        if (!strcmp (client->uri, "/en.strings")) {
            /*
             * Send strings file.
             */
            if (client->printer->strings) {
                int         fd;           /* Icon file */
                struct stat fileinfo;     /* Icon file information */
                char        buffer[4096]; /* Copy buffer */
                ssize_t     bytes;        /* Bytes */

                if (!stat (client->printer->strings, &fileinfo)
                    && (fd = open (client->printer->strings, O_RDONLY | O_BINARY)) >= 0) {
                    if (!respond_http (client, HTTP_STATUS_OK, NULL, "text/strings", (size_t)fileinfo.st_size)) {
                        close (fd);
                        return (0);
                    }

                    while ((bytes = read (fd, buffer, sizeof (buffer))) > 0)
                        httpWrite2 (client->http, buffer, (size_t)bytes);

                    httpFlushWrite (client->http);

                    close (fd);
                } else
                    return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
            } else
                return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
        } else if (!strcmp (client->uri, "/icon.png")) {
            /*
             * Send medium PNG icon file.
             */

            if (client->printer->icons[1]) {
                int         fd;           /* Icon file */
                struct stat fileinfo;     /* Icon file information */
                char        buffer[4096]; /* Copy buffer */
                ssize_t     bytes;        /* Bytes */

                if (!stat (client->printer->icons[1], &fileinfo)
                    && (fd = open (client->printer->icons[1], O_RDONLY | O_BINARY)) >= 0) {
                    if (!respond_http (client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) {
                        close (fd);
                        return (0);
                    }

                    while ((bytes = read (fd, buffer, sizeof (buffer))) > 0)
                        httpWrite2 (client->http, buffer, (size_t)bytes);

                    httpFlushWrite (client->http);

                    close (fd);
                } else
                    return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
            } else {
                fputs ("Icon file is internal printer.png.\n", stderr);

                if (!respond_http (client, HTTP_STATUS_OK, NULL, "image/png", sizeof (printer_png)))
                    return (0);

                httpWrite2 (client->http, (const char *)printer_png, sizeof (printer_png));
                httpFlushWrite (client->http);
            }
        } else if (!strcmp (client->uri, "/icon-lg.png")) {
            /*
             * Send large PNG icon file.
             */

            if (client->printer->icons[2]) {
                int         fd;           /* Icon file */
                struct stat fileinfo;     /* Icon file information */
                char        buffer[4096]; /* Copy buffer */
                ssize_t     bytes;        /* Bytes */

                if (!stat (client->printer->icons[2], &fileinfo)
                    && (fd = open (client->printer->icons[2], O_RDONLY | O_BINARY)) >= 0) {
                    if (!respond_http (client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) {
                        close (fd);
                        return (0);
                    }

                    while ((bytes = read (fd, buffer, sizeof (buffer))) > 0)
                        httpWrite2 (client->http, buffer, (size_t)bytes);

                    httpFlushWrite (client->http);

                    close (fd);
                } else
                    return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
            } else {
                fputs ("Icon file is internal printer-lg.png.\n", stderr);

                if (!respond_http (client, HTTP_STATUS_OK, NULL, "image/png", sizeof (printer_lg_png)))
                    return (0);

                httpWrite2 (client->http, (const char *)printer_lg_png, sizeof (printer_lg_png));
                httpFlushWrite (client->http);
            }
        } else if (!strcmp (client->uri, "/icon-sm.png")) {
            /*
             * Send small PNG icon file.
             */

            if (client->printer->icons[0]) {
                int         fd;           /* Icon file */
                struct stat fileinfo;     /* Icon file information */
                char        buffer[4096]; /* Copy buffer */
                ssize_t     bytes;        /* Bytes */

                if (!stat (client->printer->icons[0], &fileinfo)
                    && (fd = open (client->printer->icons[0], O_RDONLY | O_BINARY)) >= 0) {
                    if (!respond_http (client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size)) {
                        close (fd);
                        return (0);
                    }

                    while ((bytes = read (fd, buffer, sizeof (buffer))) > 0)
                        httpWrite2 (client->http, buffer, (size_t)bytes);

                    httpFlushWrite (client->http);

                    close (fd);
                } else
                    return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
            } else {
                fputs ("Icon file is internal printer-sm.png.\n", stderr);

                if (!respond_http (client, HTTP_STATUS_OK, NULL, "image/png", sizeof (printer_sm_png)))
                    return (0);

                httpWrite2 (client->http, (const char *)printer_sm_png, sizeof (printer_sm_png));
                httpFlushWrite (client->http);
            }
        } else {
            /*
             * Authenticate if needed...
             */
            if ((http_status = authenticate_request (client)) != HTTP_STATUS_CONTINUE) {
                return (respond_http (client, http_status, NULL, NULL, 0));
            }

            if (!strcmp (client->uri, "/")) {
                /*
                 * Show web status page...
                 */

                return (show_status (client));
            } else if (!strcmp (client->uri, "/media")) {
                /*
                 * Show web media page...
                 */
                return (show_media (client));
            } else if (!strcmp (client->uri, "/supplies")) {
                /*
                 * Show web supplies page...
                 */

                return (show_supplies (client));
            } else
                return (respond_http (client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
        }
        break;

    case HTTP_STATE_POST:
        if (strcmp (httpGetField (client->http, HTTP_FIELD_CONTENT_TYPE), "application/ipp")) {
            /*
             * Not an IPP request...
             */

            return (respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0));
        }

        /*
         * Read the IPP request...
         */

        client->request = ippNew ();

        while ((ipp_state = ippRead (client->http, client->request)) != IPP_STATE_DATA) {
            if (ipp_state == IPP_STATE_ERROR) {
                fprintf (stderr, "%s IPP read error (%s).\n", client->hostname, cupsLastErrorString ());
                respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
                return (0);
            }
        }

        /*
         * Now that we have the IPP request, process the request...
         */

        return (process_ipp (client));

    default:
        break; /* Anti-compiler-warning-code */
    }

    return (1);
}

/*
 * 'process_ipp()' - Process an IPP request.
 */

static int                            /* O - 1 on success, 0 on error */
process_ipp (ippeve_client_t *client) /* I - Client */
{
    ipp_tag_t        group;        /* Current group tag */
    ipp_attribute_t *attr;         /* Current attribute */
    ipp_attribute_t *charset;      /* Character set attribute */
    ipp_attribute_t *language;     /* Language attribute */
    ipp_attribute_t *uri;          /* Printer URI attribute */
    int              major, minor; /* Version number */
    const char      *name;         /* Name of attribute */
    http_status_t    status;       /* Authentication status */

    debug_attributes ("Request", client->request, 1);

    /*
     * First build an empty response message for this request...
     */

    client->operation_id = ippGetOperation (client->request);
    client->response = ippNewResponse (client->request);

    /*
     * Then validate the request header and required attributes...
     */

    major = ippGetVersion (client->request, &minor);

    if (major < 1 || major > 2) {
        /*
         * Return an error, since we only support IPP 1.x and 2.x.
         */

        respond_ipp (client, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, "Bad request version number %d.%d.", major, minor);
    } else if ((major * 10 + minor) > MaxVersion) {
        if (httpGetState (client->http) != HTTP_STATE_POST_SEND)
            httpFlush (client->http); /* Flush trailing (junk) data */

        respond_http (client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
        return (0);
    } else if (ippGetRequestId (client->request) <= 0) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad request-id %d.", ippGetRequestId (client->request));
    } else if (!ippFirstAttribute (client->request)) {
        respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "No attributes in request.");
    } else {
        /*
         * Make sure that the attributes are provided in the correct order and
         * don't repeat groups...
         */

        for (attr = ippFirstAttribute (client->request), group = ippGetGroupTag (attr); attr;
             attr = ippNextAttribute (client->request)) {
            if (ippGetGroupTag (attr) < group && ippGetGroupTag (attr) != IPP_TAG_ZERO) {
                /*
                 * Out of order; return an error...
                 */

                respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Attribute groups are out of order (%x < %x).",
                    ippGetGroupTag (attr), group);
                break;
            } else
                group = ippGetGroupTag (attr);
        }

        if (!attr) {
            /*
             * Then make sure that the first three attributes are:
             *
             *     attributes-charset
             *     attributes-natural-language
             *     printer-uri/job-uri
             */

            attr = ippFirstAttribute (client->request);
            name = ippGetName (attr);
            if (attr && name && !strcmp (name, "attributes-charset") && ippGetValueTag (attr) == IPP_TAG_CHARSET)
                charset = attr;
            else
                charset = NULL;

            attr = ippNextAttribute (client->request);
            name = ippGetName (attr);

            if (attr && name && !strcmp (name, "attributes-natural-language")
                && ippGetValueTag (attr) == IPP_TAG_LANGUAGE)
                language = attr;
            else
                language = NULL;

            if ((attr = ippFindAttribute (client->request, "printer-uri", IPP_TAG_URI)) != NULL)
                uri = attr;
            else if ((attr = ippFindAttribute (client->request, "job-uri", IPP_TAG_URI)) != NULL)
                uri = attr;
            else
                uri = NULL;

            if (charset && strcasecmp (ippGetString (charset, 0, NULL), "us-ascii")
                && strcasecmp (ippGetString (charset, 0, NULL), "utf-8")) {
                /*
                 * Bad character set...
                 */

                respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Unsupported character set \"%s\".",
                    ippGetString (charset, 0, NULL));
            } else if (!charset || !language || !uri) {
                /*
                 * Return an error, since attributes-charset,
                 * attributes-natural-language, and printer-uri/job-uri are required
                 * for all operations.
                 */

                respond_ipp (client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required attributes.");
            } else {
                char scheme[32],   /* URI scheme */
                    userpass[32],  /* Username/password in URI */
                    host[256],     /* Host name in URI */
                    resource[256]; /* Resource path in URI */
                int port;          /* Port number in URI */

                name = ippGetName (uri);

                if (httpSeparateURI (HTTP_URI_CODING_ALL, ippGetString (uri, 0, NULL), scheme, sizeof (scheme),
                        userpass, sizeof (userpass), host, sizeof (host), &port, resource, sizeof (resource))
                    < HTTP_URI_STATUS_OK)
                    respond_ipp (client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "Bad %s value '%s'.", name,
                        ippGetString (uri, 0, NULL));
                else if ((!strcmp (name, "job-uri") && strncmp (resource, "/ipp/print/", 11))
                         || (!strcmp (name, "printer-uri") && strcmp (resource, "/ipp/print")
                             && (strcmp (resource, "/")
                                 || ippGetOperation (client->request) != IPP_OP_GET_PRINTER_ATTRIBUTES)))
                    respond_ipp (
                        client, IPP_STATUS_ERROR_NOT_FOUND, "%s %s not found.", name, ippGetString (uri, 0, NULL));
                else if (client->operation_id != IPP_OP_GET_PRINTER_ATTRIBUTES
                         && (status = authenticate_request (client)) != HTTP_STATUS_CONTINUE) {
                    flush_document_data (client);

                    return (respond_http (client, status, NULL, NULL, 0));
                } else {
                    /*
                     * Handle HTTP Expect...
                     */

                    if (httpGetExpect (client->http)) {
                        if (httpGetExpect (client->http) == HTTP_STATUS_CONTINUE) {
                            /*
                             * Send 100-continue header...
                             */

                            if (!respond_http (client, HTTP_STATUS_CONTINUE, NULL, NULL, 0))
                                return (0);
                        } else {
                            /*
                             * Send 417-expectation-failed header...
                             */

                            if (!respond_http (client, HTTP_STATUS_EXPECTATION_FAILED, NULL, NULL, 0))
                                return (0);

                            flush_document_data (client);
                            return (1);
                        }
                    }

                    /*
                     * Try processing the operation...
                     */

                    switch (client->operation_id) {
                    case IPP_OP_PRINT_JOB:
                        ipp_print_job (client);
                        break;

                    case IPP_OP_PRINT_URI:
                        ipp_print_uri (client);
                        break;

                    case IPP_OP_VALIDATE_JOB:
                        ipp_validate_job (client);
                        break;

                    case IPP_OP_CREATE_JOB:
                        ipp_create_job (client);
                        break;

                    case IPP_OP_SEND_DOCUMENT:
                        ipp_send_document (client);
                        break;

                    case IPP_OP_SEND_URI:
                        ipp_send_uri (client);
                        break;

                    case IPP_OP_CANCEL_JOB:
                        ipp_cancel_job (client);
                        break;

                    case IPP_OP_CANCEL_MY_JOBS:
                        ipp_cancel_my_jobs (client);
                        break;

                    case IPP_OP_GET_JOB_ATTRIBUTES:
                        ipp_get_job_attributes (client);
                        break;

                    case IPP_OP_GET_JOBS:
                        ipp_get_jobs (client);
                        break;

                    case IPP_OP_GET_PRINTER_ATTRIBUTES:
                        ipp_get_printer_attributes (client);
                        break;

                    case IPP_OP_CLOSE_JOB:
                        ipp_close_job (client);
                        break;

                    case IPP_OP_IDENTIFY_PRINTER:
                        ipp_identify_printer (client);
                        break;

                    default:
                        respond_ipp (client, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED, "Operation not supported.");
                        break;
                    }
                }
            }
        }
    }

    /*
     * Send the HTTP header and return...
     */

    if (httpGetState (client->http) != HTTP_STATE_POST_SEND)
        httpFlush (client->http); /* Flush trailing (junk) data */

    return (respond_http (client, HTTP_STATUS_OK, NULL, "application/ipp", ippLength (client->response)));
}

/*
 * 'process_job()' - Process a print job.
 */

static void *                   /* O - Thread exit status */
process_job (ippeve_job_t *job) /* I - Job */
{
    job->state = IPP_JSTATE_PROCESSING;
    job->printer->state = IPP_PSTATE_PROCESSING;
    job->processing = time (NULL);

    while (job->printer->state_reasons & IPPEVE_PREASON_MEDIA_EMPTY) {
        job->printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED;

        sleep (1);
    }

    job->printer->state_reasons &= (ippeve_preason_t)~IPPEVE_PREASON_MEDIA_NEEDED;

    if (job->printer->command) {
        /*
         * Execute a command with the job spool file and wait for it to complete...
         */

        int pid,                    /* Process ID */
            status;                 /* Exit status */
        struct timeval start,       /* Start time */
            end;                    /* End time */
        char *myargv[3],            /* Command-line arguments */
            *myenvp[400];           /* Environment variables */
        int              myenvc;    /* Number of environment variables */
        ipp_attribute_t *attr;      /* Job attribute */
        char             val[1280], /* IPP_NAME=value */
            *valptr;                /* Pointer into string */
#ifndef _WIN32
        int  mystdout = -1; /* File for stdout */
        int  mypipe[2];     /* Pipe for stderr */
        char line[2048],    /* Line from stderr */
            *ptr,           /* Pointer into line */
            *endptr;        /* End of line */
        ssize_t bytes;      /* Bytes read */
#endif                      /* !_WIN32 */

        fprintf (stderr, "[Job %d] Running command \"%s %s\".\n", job->id, job->printer->command, job->filename);
        gettimeofday (&start, NULL);

        /*
         * Setup the command-line arguments...
         */

        myargv[0] = job->printer->command;
        myargv[1] = job->filename;
        myargv[2] = NULL;

        /*
         * Copy the current environment, then add environment variables for every
         * Job attribute and Printer -default attributes...
         */

        for (myenvc = 0; environ[myenvc] && myenvc < (int)(sizeof (myenvp) / sizeof (myenvp[0]) - 1); myenvc++)
            myenvp[myenvc] = strdup (environ[myenvc]);

        if (myenvc > (int)(sizeof (myenvp) / sizeof (myenvp[0]) - 32)) {
            fprintf (stderr, "[Job %d] Too many environment variables to process job.\n", job->id);
            job->state = IPP_JSTATE_ABORTED;
            goto error;
        }

        snprintf (val, sizeof (val), "CONTENT_TYPE=%s", job->format);
        myenvp[myenvc++] = strdup (val);

        if (job->printer->device_uri) {
            snprintf (val, sizeof (val), "DEVICE_URI=%s", job->printer->device_uri);
            myenvp[myenvc++] = strdup (val);
        }

        if (job->printer->output_format) {
            snprintf (val, sizeof (val), "OUTPUT_TYPE=%s", job->printer->output_format);
            myenvp[myenvc++] = strdup (val);
        }

#if !CUPS_LITE
        if (job->printer->ppdfile) {
            snprintf (val, sizeof (val), "PPD=%s", job->printer->ppdfile);
            myenvp[myenvc++] = strdup (val);
        }
#endif /* !CUPS_LITE */

        for (attr = ippFirstAttribute (job->printer->attrs);
             attr && myenvc < (int)(sizeof (myenvp) / sizeof (myenvp[0]) - 1);
             attr = ippNextAttribute (job->printer->attrs)) {
            /*
             * Convert "attribute-name-default" to "IPP_ATTRIBUTE_NAME_DEFAULT=" and
             * "pwg-xxx" to "IPP_PWG_XXX", then add the value(s) from the attribute.
             */

            const char *name = ippGetName (attr),
                       /* Attribute name */
                *suffix = strstr (name, "-default");
            /* Suffix on attribute name */

            if (strncmp (name, "pwg-", 4) && (!suffix || suffix[8]))
                continue;

            valptr = val;
            *valptr++ = 'I';
            *valptr++ = 'P';
            *valptr++ = 'P';
            *valptr++ = '_';
            while (*name && valptr < (val + sizeof (val) - 2)) {
                if (*name == '-')
                    *valptr++ = '_';
                else
                    *valptr++ = (char)toupper (*name & 255);

                name++;
            }
            *valptr++ = '=';
            ippAttributeString (attr, valptr, sizeof (val) - (size_t)(valptr - val));

            myenvp[myenvc++] = strdup (val);
        }

        for (attr = ippFirstAttribute (job->attrs); attr && myenvc < (int)(sizeof (myenvp) / sizeof (myenvp[0]) - 1);
             attr = ippNextAttribute (job->attrs)) {
            /*
             * Convert "attribute-name" to "IPP_ATTRIBUTE_NAME=" and then add the
             * value(s) from the attribute.
             */

            const char *name = ippGetName (attr);
            /* Attribute name */

            if (!name)
                continue;

            valptr = val;
            *valptr++ = 'I';
            *valptr++ = 'P';
            *valptr++ = 'P';
            *valptr++ = '_';
            while (*name && valptr < (val + sizeof (val) - 2)) {
                if (*name == '-')
                    *valptr++ = '_';
                else
                    *valptr++ = (char)toupper (*name & 255);

                name++;
            }
            *valptr++ = '=';
            ippAttributeString (attr, valptr, sizeof (val) - (size_t)(valptr - val));

            myenvp[myenvc++] = strdup (val);
        }

        if (attr) {
            fprintf (stderr, "[Job %d] Too many environment variables to process job.\n", job->id);
            job->state = IPP_JSTATE_ABORTED;
            goto error;
        }

        myenvp[myenvc] = NULL;

        /*
         * Now run the program...
         */

#ifdef _WIN32
        status = _spawnvpe (_P_WAIT, job->printer->command, myargv, myenvp);

#else
        if (job->printer->device_uri) {
            char scheme[32],   /* URI scheme */
                userpass[256], /* username:password (unused) */
                host[256],     /* Hostname or IP address */
                resource[256]; /* Resource path */
            int port;          /* Port number */

            if (httpSeparateURI (HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof (scheme), userpass,
                    sizeof (userpass), host, sizeof (host), &port, resource, sizeof (resource))
                < HTTP_URI_STATUS_OK) {
                fprintf (stderr, "[Job %d] Bad device URI \"%s\".\n", job->id, job->printer->device_uri);
            } else if (!strcmp (scheme, "file")) {
                struct stat fileinfo; /* See if this is a file or directory... */

                if (stat (resource, &fileinfo)) {
                    if (errno == ENOENT) {
                        if ((mystdout = open (resource, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) >= 0)
                            fprintf (stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource);
                        else
                            fprintf (
                                stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, resource, strerror (errno));
                    } else
                        fprintf (stderr, "[Job %d] Unable to access \"%s\": %s\n", job->id, resource, strerror (errno));
                } else if (S_ISDIR (fileinfo.st_mode)) {
                    if ((mystdout = create_job_file (job, line, sizeof (line), resource, "prn")) >= 0)
                        fprintf (stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, line);
                    else
                        fprintf (stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, line, strerror (errno));
                } else if (!S_ISREG (fileinfo.st_mode)) {
                    if ((mystdout = open (resource, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) >= 0)
                        fprintf (stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource);
                    else
                        fprintf (stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, resource, strerror (errno));
                } else if ((mystdout = open (resource, O_WRONLY | O_BINARY)) >= 0)
                    fprintf (stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource);
                else
                    fprintf (stderr, "[Job %d] Unable to open \"%s\": %s\n", job->id, resource, strerror (errno));
            } else if (!strcmp (scheme, "socket")) {
                http_addrlist_t *addrlist;    /* List of addresses */
                char             service[32]; /* Service number */

                snprintf (service, sizeof (service), "%d", port);

                if ((addrlist = httpAddrGetList (host, AF_UNSPEC, service)) == NULL)
                    fprintf (stderr, "[Job %d] Unable to find \"%s\": %s\n", job->id, host, cupsLastErrorString ());
                else if (!httpAddrConnect2 (addrlist, &mystdout, 30000, &(job->cancel)))
                    fprintf (
                        stderr, "[Job %d] Unable to connect to \"%s\": %s\n", job->id, host, cupsLastErrorString ());

                httpAddrFreeList (addrlist);
            } else {
                fprintf (stderr, "[Job %d] Unsupported device URI scheme \"%s\".\n", job->id, scheme);
            }
        } else if ((mystdout = create_job_file (job, line, sizeof (line), job->printer->directory, "prn")) >= 0) {
            fprintf (stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, line);
        }

        if (mystdout < 0) {
            if ((mystdout = open ("/dev/null", O_WRONLY | O_BINARY)) < 0)
                fprintf (
                    stderr, "[Job %d] Unable to redirect command output to /dev/null: %s", job->id, strerror (errno));
        }

        if (pipe (mypipe)) {
            fprintf (stderr, "[Job %d] Unable to create pipe for stderr: %s\n", job->id, strerror (errno));
            mypipe[0] = mypipe[1] = -1;
        }

        if ((pid = fork ()) == 0) {
            /*
             * Child comes here...
             */

            if (mystdout >= 0) {
                close (1);
                dup2 (mystdout, 1);
                close (mystdout);
            }

            if (mypipe[1] >= 0) {
                close (2);
                dup2 (mypipe[1], 2);
                close (mypipe[0]);
                close (mypipe[1]);
            }

            execve (job->printer->command, myargv, myenvp);
            exit (errno);
        } else if (pid < 0) {
            /*
             * Unable to fork process...
             */

            fprintf (stderr, "[Job %d] Unable to start job processing command: %s\n", job->id, strerror (errno));
            status = -1;

            close (mystdout);
            close (mypipe[0]);
            close (mypipe[1]);

            /*
             * Free memory used for environment...
             */

            while (myenvc > 0)
                free (myenvp[--myenvc]);
        } else {
            /*
             * Free memory used for environment...
             */

            while (myenvc > 0)
                free (myenvp[--myenvc]);

            /*
             * Close the output file in the parent process...
             */

            close (mystdout);

            /*
             * If the pipe exists, read from it until EOF...
             */

            if (mypipe[0] >= 0) {
                close (mypipe[1]);

                endptr = line;
                while ((bytes = read (mypipe[0], endptr, sizeof (line) - (size_t)(endptr - line) - 1)) > 0) {
                    endptr += bytes;
                    *endptr = '\0';

                    while ((ptr = strchr (line, '\n')) != NULL) {
                        int level = 3; /* Message log level */

                        *ptr++ = '\0';

                        if (!strncmp (line, "ATTR:", 5)) {
                            /*
                             * Process job/printer attribute updates.
                             */

                            process_attr_message (job, line);
                        } else if (!strncmp (line, "DEBUG:", 6)) {
                            /*
                             * Debug message...
                             */

                            level = 2;
                        } else if (!strncmp (line, "ERROR:", 6)) {
                            /*
                             * Error message...
                             */

                            level = 0;
                            job->message = strdup (line + 6);
                            job->msglevel = 0;
                        } else if (!strncmp (line, "INFO:", 5)) {
                            /*
                             * Informational/progress message...
                             */

                            level = 1;
                            if (job->msglevel) {
                                job->message = strdup (line + 5);
                                job->msglevel = 1;
                            }
                        } else if (!strncmp (line, "STATE:", 6)) {
                            /*
                             * Process printer-state-reasons keywords.
                             */

                            process_state_message (job, line);
                        }

                        if (Verbosity >= level)
                            fprintf (stderr, "[Job %d] Command - %s\n", job->id, line);

                        bytes = ptr - line;
                        if (ptr < endptr)
                            memmove (line, ptr, (size_t)(endptr - ptr));
                        endptr -= bytes;
                        *endptr = '\0';
                    }
                }

                close (mypipe[0]);
            }

            /*
             * Wait for child to complete...
             */

#ifdef HAVE_WAITPID
            while (waitpid (pid, &status, 0) < 0)
                ;
#else
            while (wait (&status) < 0)
                ;
#endif /* HAVE_WAITPID */
        }
#endif /* _WIN32 */

        if (status) {
#ifndef _WIN32
            if (WIFEXITED (status))
#endif /* !_WIN32 */
                fprintf (stderr, "[Job %d] Command \"%s\" exited with status %d.\n", job->id, job->printer->command,
                    WEXITSTATUS (status));
#ifndef _WIN32
            else
                fprintf (stderr, "[Job %d] Command \"%s\" terminated with signal %d.\n", job->id, job->printer->command,
                    WTERMSIG (status));
#endif /* !_WIN32 */
            job->state = IPP_JSTATE_ABORTED;
        } else
            fprintf (stderr, "[Job %d] Command \"%s\" completed successfully.\n", job->id, job->printer->command);

        /*
         * Report the total processing time...
         */

        gettimeofday (&end, NULL);

        fprintf (stderr, "[Job %d] Processing time was %.3f seconds.\n", job->id,
            end.tv_sec - start.tv_sec + 0.000001 * (end.tv_usec - start.tv_usec));
    } else {
        /*
         * Sleep for a random amount of time to simulate job processing.
         */

        sleep ((unsigned)(5 + (CUPS_RAND () % 11)));
    }

    if (job->cancel)
        job->state = IPP_JSTATE_CANCELED;
    else if (job->state == IPP_JSTATE_PROCESSING)
        job->state = IPP_JSTATE_COMPLETED;

error:

    job->completed = time (NULL);
    job->printer->state = IPP_PSTATE_IDLE;
    job->printer->active_job = NULL;

    return (NULL);
}

/*
 * 'process_state_message()' - Process a STATE: message from a command.
 */

static void process_state_message (ippeve_job_t *job, /* I - Job */
    char                                        *message)                                    /* I - Message */
{
    int              i;             /* Looping var */
    ippeve_preason_t state_reasons, /* printer-state-reasons values */
        bit;                        /* Current reason bit */
    char *ptr,                      /* Pointer into message */
        *next;                      /* Next keyword in message */
    int remove;                     /* Non-zero if we are removing keywords */

    /*
     * Skip leading "STATE:" and any whitespace...
     */

    for (message += 6; *message; message++)
        if (*message != ' ' && *message != '\t')
            break;

    /*
     * Support the following forms of message:
     *
     * "keyword[,keyword,...]" to set the printer-state-reasons value(s).
     *
     * "-keyword[,keyword,...]" to remove keywords.
     *
     * "+keyword[,keyword,...]" to add keywords.
     *
     * Keywords may or may not have a suffix (-report, -warning, -error) per
     * RFC 8011.
     */

    if (*message == '-') {
        remove = 1;
        state_reasons = job->printer->state_reasons;
        message++;
    } else if (*message == '+') {
        remove = 0;
        state_reasons = job->printer->state_reasons;
        message++;
    } else {
        remove = 0;
        state_reasons = IPPEVE_PREASON_NONE;
    }

    while (*message) {
        if ((next = strchr (message, ',')) != NULL)
            *next++ = '\0';

        if ((ptr = strstr (message, "-error")) != NULL)
            *ptr = '\0';
        else if ((ptr = strstr (message, "-report")) != NULL)
            *ptr = '\0';
        else if ((ptr = strstr (message, "-warning")) != NULL)
            *ptr = '\0';

        for (i = 0, bit = 1; i < (int)(sizeof (ippeve_preason_strings) / sizeof (ippeve_preason_strings[0]));
             i++, bit *= 2) {
            if (!strcmp (message, ippeve_preason_strings[i])) {
                if (remove)
                    state_reasons &= ~bit;
                else
                    state_reasons |= bit;
            }
        }

        if (next)
            message = next;
        else
            break;
    }

    job->printer->state_reasons = state_reasons;
}

/*
 * 'register_printer()' - Register a printer object via DNS-SD.
 */

static int                                   /* O - 1 on success, 0 on error */
register_printer (ippeve_printer_t *printer) /* I - Printer */
{
#ifdef HAVE_DNSSD
    ippeve_txt_t ipp_txt; /* DNS-SD IPP TXT record */
    int          i,       /* Looping var */
        count;            /* Number of values */
    ipp_attribute_t *color_supported, *document_format_supported, *printer_location, *printer_make_and_model,
        *printer_uuid, *sides_supported, *urf_supported; /* Printer attributes */
    const char *value;                                   /* Value string */
    char        adminurl[247],                           /* adminurl value */
        formats[252],                                    /* List of supported formats */
        urf[252],                                        /* List of supported URF values */
        *ptr;                                            /* Pointer into string */

    if (printer->dnssd_subtypes && !strcmp (printer->dnssd_subtypes, "off"))
        return (1);

    color_supported = ippFindAttribute (printer->attrs, "color-supported", IPP_TAG_BOOLEAN);
    document_format_supported = ippFindAttribute (printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE);
    printer_location = ippFindAttribute (printer->attrs, "printer-location", IPP_TAG_TEXT);
    printer_make_and_model = ippFindAttribute (printer->attrs, "printer-make-and-model", IPP_TAG_TEXT);
    printer_uuid = ippFindAttribute (printer->attrs, "printer-uuid", IPP_TAG_URI);
    sides_supported = ippFindAttribute (printer->attrs, "sides-supported", IPP_TAG_KEYWORD);
    urf_supported = ippFindAttribute (printer->attrs, "urf-supported", IPP_TAG_KEYWORD);

    httpAssembleURI (
        HTTP_URI_CODING_ALL, adminurl, sizeof (adminurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/");

    for (i = 0, count = ippGetCount (document_format_supported), ptr = formats; i < count; i++) {
        value = ippGetString (document_format_supported, i, NULL);

        if (!strcasecmp (value, "application/octet-stream"))
            continue;

        if (ptr > formats && ptr < (formats + sizeof (formats) - 1))
            *ptr++ = ',';

        strlcpy (ptr, value, sizeof (formats) - (size_t)(ptr - formats));
        ptr += strlen (ptr);

        if (ptr >= (formats + sizeof (formats) - 1))
            break;
    }

    urf[0] = '\0';
    for (i = 0, count = ippGetCount (urf_supported), ptr = urf; i < count; i++) {
        value = ippGetString (urf_supported, i, NULL);

        if (ptr > urf && ptr < (urf + sizeof (urf) - 1))
            *ptr++ = ',';

        strlcpy (ptr, value, sizeof (urf) - (size_t)(ptr - urf));
        ptr += strlen (ptr);

        if (ptr >= (urf + sizeof (urf) - 1))
            break;
    }

    /*
     * Rename the service as needed...
     */

    if (printer->dnssd_collision) {
        char        new_dnssd_name[256]; /* New DNS-SD name */
        const char *uuid = ippGetString (printer_uuid, 0, NULL);
        /* "printer-uuid" value */

        _cupsRWLockWrite (&printer->rwlock);

        snprintf (new_dnssd_name, sizeof (new_dnssd_name), "%s (%c%c%c%c%c%c)", printer->dnssd_name, toupper (uuid[39]),
            toupper (uuid[40]), toupper (uuid[41]), toupper (uuid[42]), toupper (uuid[43]), toupper (uuid[44]));

        free (printer->dnssd_name);
        printer->dnssd_name = strdup (new_dnssd_name);

        fprintf (stderr, "DNS-SD name collision, trying new DNS-SD service name '%s'.\n", printer->dnssd_name);

        _cupsRWUnlock (&printer->rwlock);

        printer->dnssd_collision = 0;
    }
#endif /* HAVE_DNSSD */

#ifdef HAVE_MDNSRESPONDER
    DNSServiceErrorType error;        /* Error from DNS-SD */
    char                regtype[256]; /* DNS-SD service type */
    uint32_t            ifindex;      /* Interface index */

    /*
     * Build the TXT record for IPP...
     */

    TXTRecordCreate (&ipp_txt, 1024, NULL);
    TXTRecordSetValue (&ipp_txt, "rp", 9, "ipp/print");
    if ((value = ippGetString (printer_make_and_model, 0, NULL)) != NULL)
        TXTRecordSetValue (&ipp_txt, "ty", (uint8_t)strlen (value), value);
    TXTRecordSetValue (&ipp_txt, "adminurl", (uint8_t)strlen (adminurl), adminurl);
    if ((value = ippGetString (printer_location, 0, NULL)) != NULL)
        TXTRecordSetValue (&ipp_txt, "note", (uint8_t)strlen (value), value);
    TXTRecordSetValue (&ipp_txt, "pdl", (uint8_t)strlen (formats), formats);
    TXTRecordSetValue (&ipp_txt, "Color", 1, ippGetBoolean (color_supported, 0) ? "T" : "F");
    TXTRecordSetValue (&ipp_txt, "Duplex", 1, ippGetCount (sides_supported) > 1 ? "T" : "F");
    if ((value = ippGetString (printer_uuid, 0, NULL)) != NULL)
        TXTRecordSetValue (&ipp_txt, "UUID", (uint8_t)strlen (value) - 9, value + 9);
#ifdef HAVE_TLS
    TXTRecordSetValue (&ipp_txt, "TLS", 3, "1.2");
#endif /* HAVE_TLS */
    if (urf[0])
        TXTRecordSetValue (&ipp_txt, "URF", (uint8_t)strlen (urf), urf);
    TXTRecordSetValue (&ipp_txt, "txtvers", 1, "1");
    TXTRecordSetValue (&ipp_txt, "qtotal", 1, "1");

    /*
     * Register the _printer._tcp (LPD) service type with a port number of 0 to
     * defend our service name but not actually support LPD...
     */

    ifindex
        = !strcmp (printer->hostname, "localhost") ? kDNSServiceInterfaceIndexLocalOnly : kDNSServiceInterfaceIndexAny;

    if (printer->printer_ref)
        DNSServiceRefDeallocate (printer->printer_ref);

    printer->printer_ref = DNSSDMaster;

    if ((error = DNSServiceRegister (&(printer->printer_ref),
             kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename, ifindex, printer->dnssd_name,
             "_printer._tcp", NULL /* domain */, NULL /* host */, 0 /* port */, 0 /* txtLen */, NULL /* txtRecord */,
             (DNSServiceRegisterReply)dnssd_callback, printer))
        != kDNSServiceErr_NoError) {
        _cupsLangPrintf (stderr, _ ("Unable to register \"%s.%s\": %d"), printer->dnssd_name, "_printer._tcp", error);
        return (0);
    }

    /*
     * Then register the _ipp._tcp (IPP) service type with the real port number to
     * advertise our IPP printer...
     */

    if (printer->ipp_ref)
        DNSServiceRefDeallocate (printer->ipp_ref);

    printer->ipp_ref = DNSSDMaster;

    if (printer->dnssd_subtypes && *(printer->dnssd_subtypes))
        snprintf (regtype, sizeof (regtype), "_ipp._tcp,%s", printer->dnssd_subtypes);
    else
        strlcpy (regtype, "_ipp._tcp", sizeof (regtype));

    if ((error = DNSServiceRegister (&(printer->ipp_ref),
             kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename, ifindex, printer->dnssd_name, regtype,
             NULL /* domain */, NULL /* host */, htons (printer->port), TXTRecordGetLength (&ipp_txt),
             TXTRecordGetBytesPtr (&ipp_txt), (DNSServiceRegisterReply)dnssd_callback, printer))
        != kDNSServiceErr_NoError) {
        _cupsLangPrintf (stderr, _ ("Unable to register \"%s.%s\": %d"), printer->dnssd_name, regtype, error);
        return (0);
    }

#ifdef HAVE_TLS
    /*
     * Then register the _ipps._tcp (IPP) service type with the real port number to
     * advertise our IPPS printer...
     */

    if (printer->ipps_ref)
        DNSServiceRefDeallocate (printer->ipps_ref);

    printer->ipps_ref = DNSSDMaster;

    if (printer->dnssd_subtypes && *(printer->dnssd_subtypes))
        snprintf (regtype, sizeof (regtype), "_ipps._tcp,%s", printer->dnssd_subtypes);
    else
        strlcpy (regtype, "_ipps._tcp", sizeof (regtype));

    if ((error = DNSServiceRegister (&(printer->ipps_ref),
             kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename, ifindex, printer->dnssd_name, regtype,
             NULL /* domain */, NULL /* host */, htons (printer->port), TXTRecordGetLength (&ipp_txt),
             TXTRecordGetBytesPtr (&ipp_txt), (DNSServiceRegisterReply)dnssd_callback, printer))
        != kDNSServiceErr_NoError) {
        _cupsLangPrintf (stderr, _ ("Unable to register \"%s.%s\": %d"), printer->dnssd_name, regtype, error);
        return (0);
    }
#endif /* HAVE_TLS */

    /*
     * Similarly, register the _http._tcp,_printer (HTTP) service type with the
     * real port number to advertise our IPP printer...
     */

    if (printer->http_ref)
        DNSServiceRefDeallocate (printer->http_ref);

    printer->http_ref = DNSSDMaster;

    if ((error = DNSServiceRegister (&(printer->http_ref),
             kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename, ifindex, printer->dnssd_name,
             "_http._tcp,_printer", NULL /* domain */, NULL /* host */, htons (printer->port), 0 /* txtLen */,
             NULL /* txtRecord */, (DNSServiceRegisterReply)dnssd_callback, printer))
        != kDNSServiceErr_NoError) {
        _cupsLangPrintf (
            stderr, _ ("Unable to register \"%s.%s\": %d"), printer->dnssd_name, "_http._tcp,_printer", error);
        return (0);
    }

    TXTRecordDeallocate (&ipp_txt);

#elif defined(HAVE_AVAHI)
    char temp[256]; /* Subtype service string */

    /*
     * Create the TXT record...
     */

    ipp_txt = NULL;
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "rp=ipp/print");
    if ((value = ippGetString (printer_make_and_model, 0, NULL)) != NULL)
        ipp_txt = avahi_string_list_add_printf (ipp_txt, "ty=%s", value);
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "adminurl=%s", adminurl);
    if ((value = ippGetString (printer_location, 0, NULL)) != NULL)
        ipp_txt = avahi_string_list_add_printf (ipp_txt, "note=%s", value);
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "pdl=%s", formats);
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Color=%s", ippGetBoolean (color_supported, 0) ? "T" : "F");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Duplex=%s", ippGetCount (sides_supported) > 1 ? "T" : "F");
    if ((value = ippGetString (printer_uuid, 0, NULL)) != NULL)
        ipp_txt = avahi_string_list_add_printf (ipp_txt, "UUID=%s", value + 9);
    else
        ipp_txt = avahi_string_list_add_printf (ipp_txt, "UUID=93b96dd5-32f0-3f52-7b2f-1f90be48231d");
#ifdef HAVE_TLS
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "TLS=1.2");
#endif /* HAVE_TLS */
    if (urf[0])
        ipp_txt = avahi_string_list_add_printf (ipp_txt, "URF=%s", urf);
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "txtvers=1");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "qtotal=1");
#if 1
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Bind=F");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "TBCP=T");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Binary=T");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Fax=F");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Scan=F");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Staple=F");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Sort=F");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Punch=0");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "kind=label");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "Copies=T");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "PaperCustom=T");
    ipp_txt = avahi_string_list_add_printf (ipp_txt, "priority=0");
#endif
    /*
     * Register _printer._tcp (LPD) with port 0 to reserve the service name...
     */

    avahi_threaded_poll_lock (DNSSDMaster);

    if (printer->dnssd_ref)
        avahi_entry_group_free (printer->dnssd_ref);

    printer->dnssd_ref = avahi_entry_group_new (DNSSDClient, dnssd_callback, printer);

    avahi_entry_group_add_service_strlst (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
        printer->dnssd_name, "_printer._tcp", NULL, NULL, 0, NULL);

    /*
     * Then register the _ipp._tcp (IPP)...
     */

    avahi_entry_group_add_service_strlst (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
        printer->dnssd_name, "_ipp._tcp", NULL, NULL, printer->port, ipp_txt);
    if (printer->dnssd_subtypes && *(printer->dnssd_subtypes)) {
        char *temptypes = strdup (printer->dnssd_subtypes), *start, *end;

        for (start = temptypes; *start; start = end) {
            if ((end = strchr (start, ',')) != NULL)
                *end++ = '\0';
            else
                end = start + strlen (start);

            snprintf (temp, sizeof (temp), "%s._sub._ipp._tcp", start);
            avahi_entry_group_add_service_subtype (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
                printer->dnssd_name, "_ipp._tcp", NULL, temp);
        }

        free (temptypes);
    }

#ifdef HAVE_TLS
    /*
     * _ipps._tcp (IPPS) for secure printing...
     */

    avahi_entry_group_add_service_strlst (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
        printer->dnssd_name, "_ipps._tcp", NULL, NULL, printer->port, ipp_txt);
    if (printer->dnssd_subtypes && *(printer->dnssd_subtypes)) {
        char *temptypes = strdup (printer->dnssd_subtypes), *start, *end;

        for (start = temptypes; *start; start = end) {
            if ((end = strchr (start, ',')) != NULL)
                *end++ = '\0';
            else
                end = start + strlen (start);

            snprintf (temp, sizeof (temp), "%s._sub._ipps._tcp", start);
            avahi_entry_group_add_service_subtype (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
                printer->dnssd_name, "_ipps._tcp", NULL, temp);
        }

        free (temptypes);
    }
#endif /* HAVE_TLS */

    /*
     * Finally _http.tcp (HTTP) for the web interface...
     */

    avahi_entry_group_add_service_strlst (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
        printer->dnssd_name, "_http._tcp", NULL, NULL, printer->port, NULL);
    avahi_entry_group_add_service_subtype (printer->dnssd_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
        printer->dnssd_name, "_http._tcp", NULL, "_printer._sub._http._tcp");

    /*
     * Commit it...
     */

    avahi_entry_group_commit (printer->dnssd_ref);
    avahi_threaded_poll_unlock (DNSSDMaster);

    avahi_string_list_free (ipp_txt);
#endif /* HAVE_MDNSRESPONDER */

    return (1);
}

/*
 * 'respond_http()' - Send a HTTP response.
 */

int                                              /* O - 1 on success, 0 on failure */
respond_http (ippeve_client_t *client,           /* I - Client */
    http_status_t              code,             /* I - HTTP status of response */
    const char                *content_encoding, /* I - Content-Encoding of response */
    const char                *type,             /* I - MIME media type of response */
    size_t                     length)                               /* I - Length of response */
{
    char message[1024]; /* Text message */

    fprintf (stderr, "%s %s\n", client->hostname, httpStatus (code));

    if (code == HTTP_STATUS_CONTINUE) {
        /*
         * 100-continue doesn't send any headers...
         */

        return (httpWriteResponse (client->http, HTTP_STATUS_CONTINUE) == 0);
    }

    /*
     * Format an error message...
     */

    if (!type && !length && code != HTTP_STATUS_OK && code != HTTP_STATUS_SWITCHING_PROTOCOLS) {
        snprintf (message, sizeof (message), "%d - %s\n", code, httpStatus (code));

        type = "text/plain";
        length = strlen (message);
    } else
        message[0] = '\0';

    /*
     * Send the HTTP response header...
     */

    httpClearFields (client->http);

    if (code == HTTP_STATUS_METHOD_NOT_ALLOWED || client->operation == HTTP_STATE_OPTIONS)
        httpSetField (client->http, HTTP_FIELD_ALLOW, "GET, HEAD, OPTIONS, POST");

    if (code == HTTP_STATUS_UNAUTHORIZED) {
        char value[256]; /* WWW-Authenticate value */

        snprintf (value, sizeof (value), "Basic realm=\"%s\"", PAMService);
        httpSetField (client->http, HTTP_FIELD_WWW_AUTHENTICATE, value);
    }

    if (type) {
        if (!strcmp (type, "text/html"))
            httpSetField (client->http, HTTP_FIELD_CONTENT_TYPE, "text/html; charset=utf-8");
        else
            httpSetField (client->http, HTTP_FIELD_CONTENT_TYPE, type);

        if (content_encoding)
            httpSetField (client->http, HTTP_FIELD_CONTENT_ENCODING, content_encoding);
    }

    httpSetLength (client->http, length);

    if (httpWriteResponse (client->http, code) < 0)
        return (0);

    /*
     * Send the response data...
     */

    if (message[0]) {
        /*
         * Send a plain text message.
         */

        if (httpPrintf (client->http, "%s", message) < 0)
            return (0);

        if (httpWrite2 (client->http, "", 0) < 0)
            return (0);
    } else if (client->response) {
        /*
         * Send an IPP response...
         */

        debug_attributes ("Response", client->response, 2);

        ippSetState (client->response, IPP_STATE_IDLE);

        if (ippWrite (client->http, client->response) != IPP_STATE_DATA)
            return (0);
    }

    return (1);
}

/*
 * 'respond_ignored()' - Respond with an ignored attribute.
 */

static void respond_ignored (ippeve_client_t *client, /* I - Client */
    ipp_attribute_t                          *attr)                            /* I - Attribute */
{
    ipp_attribute_t *temp; /* Copy of attribute */

    if (!ippGetStatusCode (client->response))
        respond_ipp (client, IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED, "Unsupported %s %s%s value.", ippGetName (attr),
            ippGetCount (attr) > 1 ? "1setOf " : "", ippTagString (ippGetValueTag (attr)));

    temp = ippCopyAttribute (client->response, attr, 0);
    ippSetGroupTag (client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP);
}

/*
 * 'respond_ipp()' - Send an IPP response.
 */

static void respond_ipp (ippeve_client_t *client,  /* I - Client */
    ipp_status_t                          status,  /* I - status-code */
    const char                           *message, /* I - printf-style status-message */
    ...)                                           /* I - Additional args as needed */
{
    const char *formatted = NULL; /* Formatted message */

    ippSetStatusCode (client->response, status);

    if (message) {
        va_list          ap;   /* Pointer to additional args */
        ipp_attribute_t *attr; /* New status-message attribute */

        va_start (ap, message);
        if ((attr = ippFindAttribute (client->response, "status-message", IPP_TAG_TEXT)) != NULL)
            ippSetStringfv (client->response, &attr, 0, message, ap);
        else
            attr = ippAddStringfv (
                client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, "status-message", NULL, message, ap);
        va_end (ap);

        formatted = ippGetString (attr, 0, NULL);
    }

    if (formatted)
        fprintf (stderr, "%s %s %s (%s)\n", client->hostname, ippOpString (client->operation_id),
            ippErrorString (status), formatted);
    else
        fprintf (stderr, "%s %s %s\n", client->hostname, ippOpString (client->operation_id), ippErrorString (status));
}

/*
 * 'respond_unsupported()' - Respond with an unsupported attribute.
 */

static void respond_unsupported (ippeve_client_t *client, /* I - Client */
    ipp_attribute_t                              *attr)                                /* I - Attribute */
{
    ipp_attribute_t *temp; /* Copy of attribute */

    respond_ipp (client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "Unsupported %s %s%s value.", ippGetName (attr),
        ippGetCount (attr) > 1 ? "1setOf " : "", ippTagString (ippGetValueTag (attr)));

    temp = ippCopyAttribute (client->response, attr, 0);
    ippSetGroupTag (client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP);
}


/*
 * 'run_printer()' - Run the printer service.
 */

static void run_printer (ippeve_printer_t *printer) /* I - Printer */
{
    int              num_fds;     /* Number of file descriptors */
    struct pollfd    polldata[3]; /* poll() data */
    ippeve_client_t *client;      /* New client */

#ifndef _WIN32
    /*
     * Set signal handlers for SIGINT and SIGTERM...
     */

    signal (SIGINT, signal_handler);
    signal (SIGTERM, signal_handler);
#endif // !_WIN32

    /*
     * Setup poll() data for the DNS-SD service socket and IPv4/6 listeners...
     */

    polldata[0].fd = printer->ipv4;
    polldata[0].events = POLLIN;

    polldata[1].fd = printer->ipv6;
    polldata[1].events = POLLIN;

    num_fds = 2;

#ifdef HAVE_MDNSRESPONDER
    polldata[num_fds].fd = DNSServiceRefSockFD (DNSSDMaster);
    polldata[num_fds++].events = POLLIN;
#endif /* HAVE_MDNSRESPONDER */

    /*
     * Loop until we are killed or have a hard error...
     */

    for (;;) {
        if (poll (polldata, (nfds_t)num_fds, 1000) < 0 && errno != EINTR) {
            perror ("poll() failed");
            break;
        }

#ifndef _WIN32
        if (StopPrinter)
            break;
#endif // !_WIN32

        if (polldata[0].revents & POLLIN) {
            if ((client = create_client (printer, printer->ipv4)) != NULL) {
                _cups_thread_t t = _cupsThreadCreate ((_cups_thread_func_t)process_client, client);

                if (t) {
                    _cupsThreadDetach (t);
                } else {
                    perror ("Unable to create client thread");
                    delete_client (client);
                }
            }
        }

        if (polldata[1].revents & POLLIN) {
            if ((client = create_client (printer, printer->ipv6)) != NULL) {
                _cups_thread_t t = _cupsThreadCreate ((_cups_thread_func_t)process_client, client);

                if (t) {
                    _cupsThreadDetach (t);
                } else {
                    perror ("Unable to create client thread");
                    delete_client (client);
                }
            }
        }

        /*
         * Process DNS-SD messages...
         */

#ifdef HAVE_MDNSRESPONDER
        if (polldata[2].revents & POLLIN)
            DNSServiceProcessResult (DNSSDMaster);
#endif /* HAVE_MDNSRESPONDER */

#ifdef HAVE_DNSSD
        if (printer->dnssd_collision)
            register_printer (printer);
#endif /* HAVE_DNSSD */

        /*
         * Clean out old jobs...
         */

        clean_jobs (printer);
    }
}

/*
 * 'show_media()' - Show media load state.
 */

static int                           /* O - 1 on success, 0 on failure */
show_media (ippeve_client_t *client) /* I - Client connection */
{
    ippeve_printer_t *printer = client->printer;
    /* Printer */
    int               i, j,           /* Looping vars */
        num_ready,                    /* Number of ready media */
        num_sizes,                    /* Number of media sizes */
        num_sources,                  /* Number of media sources */
        num_types;                    /* Number of media types */
    ipp_attribute_t *media_col_ready, /* media-col-ready attribute */
        *media_ready,                 /* media-ready attribute */
        *media_sizes,                 /* media-supported attribute */
        *media_sources,               /* media-source-supported attribute */
        *media_types,                 /* media-type-supported attribute */
        *input_tray;                  /* printer-input-tray attribute */
    ipp_t      *media_col;            /* media-col value */
    const char *media_size,           /* media value */
        *media_source,                /* media-source value */
        *media_type,                  /* media-type value */
        *ready_size,                  /* media-col-ready media-size[-name] value */
        *ready_source,                /* media-col-ready media-source value */
        *ready_tray,                  /* printer-input-tray value */
        *ready_type;                  /* media-col-ready media-type value */
    char tray_str[1024],              /* printer-input-tray string value */
        *tray_ptr;                    /* Pointer into value */
    int              tray_len;        /* Length of printer-input-tray value */
    int              ready_sheets;    /* printer-input-tray sheets value */
    int              num_options = 0; /* Number of form options */
    cups_option_t   *options = NULL;  /* Form options */
    static const int sheets[] =       /* Number of sheets */
        {250, 125, 50, 25, 5, 0, -2};
    if (!respond_http (client, HTTP_STATUS_OK, NULL, "text/html", 0))
        return (0);
    html_header (client, printer->name, 0);
    if ((media_col_ready = ippFindAttribute (printer->attrs, "media-col-ready", IPP_TAG_BEGIN_COLLECTION)) == NULL) {
        html_printf (client, "<p>Error: No media-col-ready defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    media_ready = ippFindAttribute (printer->attrs, "media-ready", IPP_TAG_ZERO);

    if ((media_sizes = ippFindAttribute (printer->attrs, "media-supported", IPP_TAG_ZERO)) == NULL) {
        html_printf (client, "<p>Error: No media-supported defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    if ((media_sources = ippFindAttribute (printer->attrs, "media-source-supported", IPP_TAG_ZERO)) == NULL) {
        html_printf (client, "<p>Error: No media-source-supported defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    if ((media_types = ippFindAttribute (printer->attrs, "media-type-supported", IPP_TAG_ZERO)) == NULL) {
        html_printf (client, "<p>Error: No media-type-supported defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    if ((input_tray = ippFindAttribute (printer->attrs, "printer-input-tray", IPP_TAG_STRING)) == NULL) {
        html_printf (client, "<p>Error: No printer-input-tray defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    num_sources = ippGetCount (media_sources);

    /*
     * Make sure the number of trays is consistent.
     */

    if (num_sources != ippGetCount (input_tray)) {
        html_printf (client,
            "<p>Error: Different number of trays in media-source-supported and printer-input-tray defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    num_ready = ippGetCount (media_col_ready);
    num_sizes = ippGetCount (media_sizes);
    num_types = ippGetCount (media_types);
    /*
     * Process form data if present...
     */
    if (printer->web_forms) {
        num_options = parse_options (client, &options);
    }
    if (num_options > 0) {
        /*
         * WARNING: A real printer/server implementation MUST NOT implement
         * media updates via a GET request - GET requests are supposed to be
         * idempotent (without side-effects) and we obviously are not
         * authenticating access here.  This form is provided solely to
         * enable testing and development!
         */
        char         name[255]; /* Form name */
        const char  *val;       /* Form value */
        pwg_media_t *media;     /* Media info */

        _cupsRWLockWrite (&printer->rwlock);

        ippDeleteAttribute (printer->attrs, media_col_ready);
        media_col_ready = NULL;

        if (media_ready) {
            ippDeleteAttribute (printer->attrs, media_ready);
            media_ready = NULL;
        }

        printer->state_reasons &= (ippeve_preason_t) ~(
            IPPEVE_PREASON_MEDIA_LOW | IPPEVE_PREASON_MEDIA_EMPTY | IPPEVE_PREASON_MEDIA_NEEDED);

        for (i = 0; i < num_sources; i++) {
            media_source = ippGetString (media_sources, i, NULL);

            if (!strcmp (media_source, "auto") || !strcmp (media_source, "manual")
                || strstr (media_source, "-man") != NULL)
                continue;

            snprintf (name, sizeof (name), "size%d", i);
            if ((media_size = cupsGetOption (name, num_options, options)) != NULL
                && (media = pwgMediaForPWG (media_size)) != NULL) {
                snprintf (name, sizeof (name), "type%d", i);
                if ((media_type = cupsGetOption (name, num_options, options)) != NULL && !*media_type)
                    media_type = NULL;

                if (media_ready)
                    ippSetString (printer->attrs, &media_ready, ippGetCount (media_ready), media_size);
                else
                    media_ready = ippAddString (
                        printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, media_size);

                media_col = create_media_col (
                    media_size, media_source, media_type, media->width, media->length, -1, -1, -1, -1);

                if (media_col_ready)
                    ippSetCollection (printer->attrs, &media_col_ready, ippGetCount (media_col_ready), media_col);
                else
                    media_col_ready = ippAddCollection (printer->attrs, IPP_TAG_PRINTER, "media-col-ready", media_col);
                ippDelete (media_col);
            } else
                media = NULL;

            snprintf (name, sizeof (name), "level%d", i);
            if ((val = cupsGetOption (name, num_options, options)) != NULL)
                ready_sheets = atoi (val);
            else
                ready_sheets = 0;

            snprintf (tray_str, sizeof (tray_str),
                "type=sheetFeedAuto%sRemovableTray;mediafeed=%d;mediaxfeed=%d;maxcapacity=%d;level=%d;status=0;name=%s;",
                !strcmp (media_source, "by-pass-tray") ? "Non" : "", media ? media->length : 0,
                media ? media->width : 0, strcmp (media_source, "by-pass-tray") ? 250 : 25, ready_sheets, media_source);

            ippSetOctetString (printer->attrs, &input_tray, i, tray_str, (int)strlen (tray_str));

            if (ready_sheets == 0) {
                printer->state_reasons |= IPPEVE_PREASON_MEDIA_EMPTY;
                if (printer->active_job)
                    printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED;
            } else if (ready_sheets < 25 && ready_sheets > 0)
                printer->state_reasons |= IPPEVE_PREASON_MEDIA_LOW;
        }

        if (!media_col_ready)
            media_col_ready = ippAddOutOfBand (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-col-ready");

        if (!media_ready)
            media_ready = ippAddOutOfBand (printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-ready");

        _cupsRWUnlock (&printer->rwlock);
    }

    if (printer->web_forms)
        html_printf (client, "<form method=\"GET\" action=\"/media\">\n");

    html_printf (client, "<table class=\"form\" summary=\"Media\">\n");
    for (i = 0; i < num_sources; i++) {
        media_source = ippGetString (media_sources, i, NULL);

        if (!strcmp (media_source, "auto") || !strcmp (media_source, "manual") || strstr (media_source, "-man") != NULL)
            continue;

        for (j = 0, ready_size = NULL, ready_type = NULL; j < num_ready; j++) {
            media_col = ippGetCollection (media_col_ready, j);
            ready_size = ippGetString (ippFindAttribute (media_col, "media-size-name", IPP_TAG_ZERO), 0, NULL);
            ready_source = ippGetString (ippFindAttribute (media_col, "media-source", IPP_TAG_ZERO), 0, NULL);
            ready_type = ippGetString (ippFindAttribute (media_col, "media-type", IPP_TAG_ZERO), 0, NULL);

            if (ready_source && !strcmp (ready_source, media_source))
                break;

            ready_source = NULL;
            ready_size = NULL;
            ready_type = NULL;
        }
        html_printf (client, "<tr><th>%s:</th>", media_source);

        /*
         * Media size...
         */

        if (printer->web_forms) {
            html_printf (client, "<td><select name=\"size%d\"><option value=\"\">None</option>", i);
            for (j = 0; j < num_sizes; j++) {
                media_size = ippGetString (media_sizes, j, NULL);

                html_printf (client, "<option%s>%s</option>",
                    (ready_size && !strcmp (ready_size, media_size)) ? " selected" : "", media_size);
            }
            html_printf (client, "</select>");
        } else
            html_printf (client, "<td>%s", ready_size);

        /*
         * Media type...
         */

        if (printer->web_forms) {
            html_printf (client, " <select name=\"type%d\"><option value=\"\">None</option>", i);
            for (j = 0; j < num_types; j++) {
                media_type = ippGetString (media_types, j, NULL);

                html_printf (client, "<option%s>%s</option>",
                    (ready_type && !strcmp (ready_type, media_type)) ? " selected" : "", media_type);
            }
            html_printf (client, "</select>");
        } else if (ready_type)
            html_printf (client, ", %s", ready_type);

        /*
         * Level/sheets loaded...
         */

        if ((ready_tray = ippGetOctetString (input_tray, i, &tray_len)) != NULL) {
            if (tray_len > (int)(sizeof (tray_str) - 1))
                tray_len = (int)sizeof (tray_str) - 1;
            memcpy (tray_str, ready_tray, (size_t)tray_len);
            tray_str[tray_len] = '\0';

            if ((tray_ptr = strstr (tray_str, "level=")) != NULL)
                ready_sheets = atoi (tray_ptr + 6);
            else
                ready_sheets = 0;
        } else
            ready_sheets = 0;

        if (printer->web_forms) {
            html_printf (client, " <select name=\"level%d\">", i);
            for (j = 0; j < (int)(sizeof (sheets) / sizeof (sheets[0])); j++) {
                if (!strcmp (media_source, "by-pass-tray") && sheets[j] > 25)
                    continue;

                if (sheets[j] < 0)
                    html_printf (client, "<option value=\"%d\"%s>Unknown</option>", sheets[j],
                        sheets[j] == ready_sheets ? " selected" : "");
                else
                    html_printf (client, "<option value=\"%d\"%s>%d sheets</option>", sheets[j],
                        sheets[j] == ready_sheets ? " selected" : "", sheets[j]);
            }
            html_printf (client, "</select></td></tr>\n");
        } else if (ready_sheets == 1)
            html_printf (client, ", 1 sheet</td></tr>\n");
        else if (ready_sheets > 0)
            html_printf (client, ", %d sheets</td></tr>\n", ready_sheets);
        else
            html_printf (client, "</td></tr>\n");
    }

    if (printer->web_forms) {
        html_printf (client, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\">");
        if (num_options > 0)
            html_printf (client, " <span class=\"badge\" id=\"status\">Media updated.</span>\n");
        html_printf (client, "</td></tr></table></form>\n");

        if (num_options > 0)
            html_printf (client, "<script>\n"
                                 "setTimeout(hide_status, 3000);\n"
                                 "function hide_status() {\n"
                                 "  var status = document.getElementById('status');\n"
                                 "  status.style.display = 'none';\n"
                                 "}\n"
                                 "</script>\n");
    } else
        html_printf (client, "</table>\n");

    cupsFreeOptions (num_options, options);

    html_footer (client);

    return (1);
}

/*
 * 'show_status()' - Show printer/system state.
 */

static int                            /* O - 1 on success, 0 on failure */
show_status (ippeve_client_t *client) /* I - Client connection */
{
    ippeve_printer_t        *printer = client->printer;
    /* Printer */
    ippeve_job_t            *job;        /* Current job */
    int                      i;          /* Looping var */
    ippeve_preason_t         reason;     /* Current reason */
    static const char *const reasons[] = /* Reason strings */
        {"Other", "Cover Open", "Input Tray Missing", "Marker Supply Empty", "Marker Supply Low",
            "Marker Waste Almost Full", "Marker Waste Full", "Media Empty", "Media Jam", "Media Low", "Media Needed",
            "Moving to Paused", "Paused", "Spool Area Full", "Toner Empty", "Toner Low"};
    static const char *const state_colors[] = {
        /* State colors */
        "#0C0", /* Idle */
        "#EE0", /* Processing */
        "#C00"  /* Stopped */
    };

    if (!respond_http (client, HTTP_STATUS_OK, NULL, "text/html", 0))
        return (0);

    html_header (client, printer->name, printer->state == IPP_PSTATE_PROCESSING ? 5 : 15);
    html_printf (client,
        "<h1><img style=\"background: %s; border-radius: 10px; float: left; margin-right: 10px; padding: 10px;\" src=\"/icon.png\" width=\"64\" height=\"64\">%s Jobs</h1>\n",
        state_colors[printer->state - IPP_PSTATE_IDLE], printer->name);
    html_printf (client, "<p>%s, %d job(s).",
        printer->state == IPP_PSTATE_IDLE         ? "Idle"
        : printer->state == IPP_PSTATE_PROCESSING ? "Printing"
                                                  : "Stopped",
        cupsArrayCount (printer->jobs));
    for (i = 0, reason = 1; i < (int)(sizeof (reasons) / sizeof (reasons[0])); i++, reason <<= 1)
        if (printer->state_reasons & reason)
            html_printf (client, "\n<br>&nbsp;&nbsp;&nbsp;&nbsp;%s", reasons[i]);
    html_printf (client, "</p>\n");

    if (cupsArrayCount (printer->jobs) > 0) {
        _cupsRWLockRead (&(printer->rwlock));

        html_printf (client,
            "<table class=\"striped\" summary=\"Jobs\"><thead><tr><th>Job #</th><th>Name</th><th>Owner</th><th>Status</th></tr></thead><tbody>\n");
        for (job = (ippeve_job_t *)cupsArrayFirst (printer->jobs); job;
             job = (ippeve_job_t *)cupsArrayNext (printer->jobs)) {
            char when[256], /* When job queued/started/finished */
                hhmmss[64]; /* Time HH:MM:SS */

            switch (job->state) {
            case IPP_JSTATE_PENDING:
            case IPP_JSTATE_HELD:
                snprintf (when, sizeof (when), "Queued at %s", time_string (job->created, hhmmss, sizeof (hhmmss)));
                break;
            case IPP_JSTATE_PROCESSING:
            case IPP_JSTATE_STOPPED:
                snprintf (when, sizeof (when), "Started at %s", time_string (job->processing, hhmmss, sizeof (hhmmss)));
                break;
            case IPP_JSTATE_ABORTED:
                snprintf (when, sizeof (when), "Aborted at %s", time_string (job->completed, hhmmss, sizeof (hhmmss)));
                break;
            case IPP_JSTATE_CANCELED:
                snprintf (when, sizeof (when), "Canceled at %s", time_string (job->completed, hhmmss, sizeof (hhmmss)));
                break;
            case IPP_JSTATE_COMPLETED:
                snprintf (
                    when, sizeof (when), "Completed at %s", time_string (job->completed, hhmmss, sizeof (hhmmss)));
                break;
            }

            html_printf (client, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", job->id, job->name,
                job->username, when);
        }
        html_printf (client, "</tbody></table>\n");

        _cupsRWUnlock (&(printer->rwlock));
    }

    html_footer (client);

    return (1);
}

/*
 * 'show_supplies()' - Show printer supplies.
 */

static int                              /* O - 1 on success, 0 on failure */
show_supplies (ippeve_client_t *client) /* I - Client connection */
{
    ippeve_printer_t *printer = client->printer;
    /* Printer */
    int               i,                         /* Looping var */
        num_supply;                              /* Number of supplies */
    ipp_attribute_t *supply,                     /* printer-supply attribute */
        *supply_desc;                            /* printer-supply-description attribute */
    int            num_options = 0;              /* Number of form options */
    cups_option_t *options = NULL;               /* Form options */
    int            supply_len,                   /* Length of supply value */
        level;                                   /* Supply level */
    const char *supply_value;                    /* Supply value */
    char        supply_text[1024],               /* Supply string */
        *supply_ptr;                             /* Pointer into supply string */
    static const char *const printer_supply[] = {/* printer-supply values */
        "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
        "maxcapacity=100;level=%d;colorantname=unknown;",
        "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
        "maxcapacity=100;level=%d;colorantname=black;",
        "index=3;class=supplyThatIsConsumed;type=toner;unit=percent;"
        "maxcapacity=100;level=%d;colorantname=cyan;",
        "index=4;class=supplyThatIsConsumed;type=toner;unit=percent;"
        "maxcapacity=100;level=%d;colorantname=magenta;",
        "index=5;class=supplyThatIsConsumed;type=toner;unit=percent;"
        "maxcapacity=100;level=%d;colorantname=yellow;"};
    static const char *const backgrounds[] = {/* Background colors for the supply-level bars */
        "#777 linear-gradient(#333,#777)", "#000 linear-gradient(#666,#000)", "#0FF linear-gradient(#6FF,#0FF)",
        "#F0F linear-gradient(#F6F,#F0F)", "#CC0 linear-gradient(#EE6,#EE0)"};
    static const char *const colors[] = /* Text colors for the supply-level bars */
        {"#fff", "#fff", "#000", "#000", "#000"};

    if (!respond_http (client, HTTP_STATUS_OK, NULL, "text/html", 0))
        return (0);

    html_header (client, printer->name, 0);

    if ((supply = ippFindAttribute (printer->attrs, "printer-supply", IPP_TAG_STRING)) == NULL) {
        html_printf (client, "<p>Error: No printer-supply defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    num_supply = ippGetCount (supply);

    if ((supply_desc = ippFindAttribute (printer->attrs, "printer-supply-description", IPP_TAG_TEXT)) == NULL) {
        html_printf (client, "<p>Error: No printer-supply-description defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    if (num_supply != ippGetCount (supply_desc)) {
        html_printf (client,
            "<p>Error: Different number of values for printer-supply and printer-supply-description defined for printer.</p>\n");
        html_footer (client);
        return (1);
    }

    if (printer->web_forms)
        num_options = parse_options (client, &options);

    if (num_options > 0) {
        /*
         * WARNING: A real printer/server implementation MUST NOT implement
         * supply updates via a GET request - GET requests are supposed to be
         * idempotent (without side-effects) and we obviously are not
         * authenticating access here.  This form is provided solely to
         * enable testing and development!
         */

        char        name[64]; /* Form field */
        const char *val;      /* Form value */

        _cupsRWLockWrite (&printer->rwlock);

        ippDeleteAttribute (printer->attrs, supply);
        supply = NULL;

        printer->state_reasons
            &= (ippeve_preason_t) ~(IPPEVE_PREASON_MARKER_SUPPLY_EMPTY | IPPEVE_PREASON_MARKER_SUPPLY_LOW
                                    | IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL | IPPEVE_PREASON_MARKER_WASTE_FULL
                                    | IPPEVE_PREASON_TONER_EMPTY | IPPEVE_PREASON_TONER_LOW);

        for (i = 0; i < num_supply; i++) {
            snprintf (name, sizeof (name), "supply%d", i);
            if ((val = cupsGetOption (name, num_options, options)) != NULL) {
                level = atoi (val); /* New level */

                snprintf (supply_text, sizeof (supply_text), printer_supply[i], level);
                if (supply)
                    ippSetOctetString (
                        printer->attrs, &supply, ippGetCount (supply), supply_text, (int)strlen (supply_text));
                else
                    supply = ippAddOctetString (
                        printer->attrs, IPP_TAG_PRINTER, "printer-supply", supply_text, (int)strlen (supply_text));

                if (i == 0) {
                    if (level == 100)
                        printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_FULL;
                    else if (level > 90)
                        printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL;
                } else {
                    if (level == 0)
                        printer->state_reasons |= IPPEVE_PREASON_TONER_EMPTY;
                    else if (level < 10)
                        printer->state_reasons |= IPPEVE_PREASON_TONER_LOW;
                }
            }
        }

        _cupsRWUnlock (&printer->rwlock);
    }

    if (printer->web_forms)
        html_printf (client, "<form method=\"GET\" action=\"/supplies\">\n");

    html_printf (client, "<table class=\"form\" summary=\"Supplies\">\n");
    for (i = 0; i < num_supply; i++) {
        supply_value = ippGetOctetString (supply, i, &supply_len);
        if (supply_len > (int)(sizeof (supply_text) - 1))
            supply_len = (int)sizeof (supply_text) - 1;

        memcpy (supply_text, supply_value, (size_t)supply_len);
        supply_text[supply_len] = '\0';

        if ((supply_ptr = strstr (supply_text, "level=")) != NULL)
            level = atoi (supply_ptr + 6);
        else
            level = 50;

        if (printer->web_forms)
            html_printf (client, "<tr><th>%s:</th><td><input name=\"supply%d\" size=\"3\" value=\"%d\"></td>",
                ippGetString (supply_desc, i, NULL), i, level);
        else
            html_printf (client, "<tr><th>%s:</th>", ippGetString (supply_desc, i, NULL));

        if (level < 10)
            html_printf (client,
                "<td class=\"meter\"><span class=\"bar\" style=\"background: %s; padding: 5px %dpx;\"></span>&nbsp;%d%%</td></tr>\n",
                backgrounds[i], level * 2, level);
        else
            html_printf (client,
                "<td class=\"meter\"><span class=\"bar\" style=\"background: %s; color: %s; padding: 5px %dpx;\">%d%%</span></td></tr>\n",
                backgrounds[i], colors[i], level * 2, level);
    }

    if (printer->web_forms) {
        html_printf (client, "<tr><td></td><td colspan=\"2\"><input type=\"submit\" value=\"Update Supplies\">");
        if (num_options > 0)
            html_printf (client, " <span class=\"badge\" id=\"status\">Supplies updated.</span>\n");
        html_printf (client, "</td></tr>\n</table>\n</form>\n");

        if (num_options > 0)
            html_printf (client, "<script>\n"
                                 "setTimeout(hide_status, 3000);\n"
                                 "function hide_status() {\n"
                                 "  var status = document.getElementById('status');\n"
                                 "  status.style.display = 'none';\n"
                                 "}\n"
                                 "</script>\n");
    } else
        html_printf (client, "</table>\n");

    cupsFreeOptions (num_options, options);

    html_footer (client);

    return (1);
}

#ifndef _WIN32
/*
 * 'signal_handler()' - Handle termination signals.
 */

static void signal_handler (int signum) /* I - Signal number (not used) */
{
    (void)signum;

    StopPrinter = 1;
}
#endif // !_WIN32

/*
 * 'time_string()' - Return the local time in hours, minutes, and seconds.
 */

static char *time_string (time_t tv,     /* I - Time value */
    char                        *buffer, /* I - Buffer */
    size_t                       bufsize)                      /* I - Size of buffer */
{
    struct tm date; /* Local time and date */

    localtime_r (&tv, &date);

    strftime (buffer, bufsize, "%X", &date);

    return (buffer);
}

/*
 * 'usage()' - Show program usage.
 */

static void usage (int status) /* O - Exit status */
{
    _cupsLangPuts (stdout, _ ("Usage: ippeveprinter [options] \"name\""));
    _cupsLangPuts (stdout, _ ("Options:"));
    _cupsLangPuts (stdout, _ ("--help                  Show program help"));
    _cupsLangPuts (stdout, _ ("--no-web-forms          Disable web forms for media and supplies"));
    _cupsLangPuts (stdout, _ ("--pam-service service   Use the named PAM service"));
    _cupsLangPuts (stdout, _ ("--version               Show program version"));
    _cupsLangPuts (stdout, _ ("-2                      Set 2-sided printing support (default=1-sided)"));
    _cupsLangPuts (stdout, _ ("-A                      Enable authentication"));
    _cupsLangPuts (stdout, _ ("-D device-uri           Set the device URI for the printer"));
    _cupsLangPuts (stdout, _ ("-F output-type/subtype  Set the output format for the printer"));
#ifdef HAVE_TLS
    _cupsLangPuts (stdout, _ ("-K keypath              Set location of server X.509 certificates and keys."));
#endif /* HAVE_TLS */
    _cupsLangPuts (stdout, _ ("-M manufacturer         Set manufacturer name (default=Test)"));
#if !CUPS_LITE
    _cupsLangPuts (stdout, _ ("-P filename.ppd         Load printer attributes from PPD file"));
#endif /* !CUPS_LITE */
    _cupsLangPuts (stdout, _ ("-S filename.strings     Set strings file"));
    _cupsLangPuts (stdout, _ ("-V version              Set default IPP version"));
    _cupsLangPuts (stdout, _ ("-a filename.conf        Load printer attributes from conf file"));
    _cupsLangPuts (stdout, _ ("-c command              Set print command"));
    _cupsLangPuts (stdout, _ ("-d spool-directory      Set spool directory"));
    _cupsLangPuts (stdout, _ ("-f type/subtype[,...]   Set supported file types"));
    _cupsLangPuts (stdout, _ ("-i iconfile.png[,...]   Set icon file(s)"));
    _cupsLangPuts (stdout, _ ("-k                      Keep job spool files"));
    _cupsLangPuts (stdout, _ ("-l location             Set location of printer"));
    _cupsLangPuts (stdout, _ ("-m model                Set model name (default=Printer)"));
    _cupsLangPuts (stdout, _ ("-n hostname             Set hostname for printer"));
    _cupsLangPuts (stdout, _ ("-p port                 Set port number for printer"));
    _cupsLangPuts (stdout, _ ("-r subtype,[subtype]    Set DNS-SD service subtype"));
    _cupsLangPuts (stdout, _ ("-s speed[,color-speed]  Set speed in pages per minute"));
    _cupsLangPuts (stdout, _ ("-v                      Be verbose"));

    exit (status);
}

/*
 * 'valid_doc_attributes()' - Determine whether the document attributes are
 *                            valid.
 *
 * When one or more document attributes are invalid, this function adds a
 * suitable response and attributes to the unsupported group.
 */

static int                                     /* O - 1 if valid, 0 if not */
valid_doc_attributes (ippeve_client_t *client) /* I - Client */
{
    int              valid = 1; /* Valid attributes? */
    ipp_op_t         op = ippGetOperation (client->request);
    /* IPP operation */
    const char      *op_name = ippOpString (op);
    /* IPP operation name */
    ipp_attribute_t *attr, /* Current attribute */
        *supported;        /* xxx-supported attribute */
    const char *compression = NULL; /* compression value */
    const char* format = NULL; /* document-format value */

    /*
     * Check operation attributes...
     */
    if ((attr = ippFindAttribute (client->request, "compression", IPP_TAG_ZERO)) != NULL) {
        /*
         * If compression is specified, only accept a supported value in a Print-Job
         * or Send-Document request...
         */

        compression = ippGetString (attr, 0, NULL);
        supported = ippFindAttribute (client->printer->attrs, "compression-supported", IPP_TAG_KEYWORD);
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_KEYWORD
            || ippGetGroupTag (attr) != IPP_TAG_OPERATION
            || (op != IPP_OP_PRINT_JOB && op != IPP_OP_SEND_DOCUMENT && op != IPP_OP_VALIDATE_JOB)
            || !ippContainsString (supported, compression)) {
                respond_ipp(client,IPP_URI_SCHEME,"client-error-uri-scheme-not-supported");
                // respond_unsupported (client, attr);
            valid = 0;
        } else {
            fprintf (stderr, "%s %s compression=\"%s\"\n", client->hostname, op_name, compression);

            ippAddString (client->request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "compression-supplied", NULL, compression);

            if (strcmp (compression, "none")) {
                if (Verbosity)
                    fprintf (stderr, "Receiving job file with \"%s\" compression.\n", compression);
                httpSetField (client->http, HTTP_FIELD_CONTENT_ENCODING, compression);
            }
        }
    }
    /*
     * Is it a format we support?
     */

    if ((attr = ippFindAttribute (client->request, "document-format", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_MIMETYPE
            || ippGetGroupTag (attr) != IPP_TAG_OPERATION) {
            respond_unsupported (client, attr);
            valid = 0;
        } else {
            format = ippGetString (attr, 0, NULL);

            fprintf (stderr, "%s %s document-format=\"%s\"\n", client->hostname, op_name, format);

            ippAddString (client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-supplied", NULL, format);
        }
    } else {
        format = ippGetString (
            ippFindAttribute (client->printer->attrs, "document-format-default", IPP_TAG_MIMETYPE), 0, NULL);
        if (!format)
            format = "application/octet-stream"; /* Should never happen */

        attr = ippAddString (client->request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, format);
    }

    if (format && !strcmp (format, "application/octet-stream")
        && (ippGetOperation (client->request) == IPP_OP_PRINT_JOB
            || ippGetOperation (client->request) == IPP_OP_SEND_DOCUMENT)) {
        /*
         * Auto-type the file using the first 8 bytes of the file...
         */

        unsigned char header[8]; /* First 8 bytes of file */

        memset (header, 0, sizeof (header));
        httpPeek (client->http, (char *)header, sizeof (header));

        fprintf (stderr, "%s %s Auto-type header: %02X%02X%02X%02X%02X%02X%02X%02X\n", client->hostname, op_name,
            header[0], header[1], header[2], header[3], header[4], header[5], header[6], header[7]);
        if (!memcmp (header, "%PDF", 4))
            format = "application/pdf";
        else if (!memcmp (header, "%!", 2))
            format = "application/postscript";
        else if (!memcmp (header, "\377\330\377", 3) && header[3] >= 0xe0 && header[3] <= 0xef)
            format = "image/jpeg";
        else if (!memcmp (header, "\211PNG", 4))
            format = "image/png";
        else if (!memcmp (header, "RaS2PwgR", 8))
            format = "image/pwg-raster";
        else if (!memcmp (header, "UNIRAST", 8))
            format = "image/urf";
        else
            format = NULL;

        if (format) {
            fprintf (stderr, "%s %s Auto-typed document-format=\"%s\"\n", client->hostname, op_name, format);

            ippAddString (client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-detected", NULL, format);
        }
    }

    if (op != IPP_OP_CREATE_JOB
        && (supported = ippFindAttribute (client->printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE))
               != NULL
        && !ippContainsString (supported, format)) {
        respond_unsupported (client, attr);
        //respond_ipp(client,IPP_DOCUMENT_UNPRINTABLE_ERROR,"aborted-by-system");
        valid = 0;
    }

    /*
     * document-name
     */

    if ((attr = ippFindAttribute (client->request, "document-name", IPP_TAG_NAME)) != NULL)
        ippAddString (
            client->request, IPP_TAG_JOB, IPP_TAG_NAME, "document-name-supplied", NULL, ippGetString (attr, 0, NULL));

    return (valid);
}

/*
 * 'valid_job_attributes()' - Determine whether the job attributes are valid.
 *
 * When one or more job attributes are invalid, this function adds a suitable
 * response and attributes to the unsupported group.
 */

static int                                     /* O - 1 if valid, 0 if not */
valid_job_attributes (ippeve_client_t *client) /* I - Client */
{
    int i,                 /* Looping var */
        count,             /* Number of values */
        fidelity,          /* "ipp-attribute-fidelity" value */
        valid = 1;         /* Valid attributes? */
    ipp_attribute_t *attr, /* Current attribute */
        *supported;        /* xxx-supported attribute */

    /*
     * Check operation attributes...
     */

    valid = valid_doc_attributes (client);

    /*
     * Check the various job template attributes...
     */

    if ((attr = ippFindAttribute (client->request, "ipp-attribute-fidelity", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_BOOLEAN) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    fidelity = ippGetBoolean (attr, 0);

    if ((attr = ippFindAttribute (client->request, "copies", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_INTEGER || ippGetInteger (attr, 0) < 1
            || ippGetInteger (attr, 0) > 999) {
            if (fidelity) {
                respond_unsupported (client, attr);
                valid = 0;
            } else {
                respond_ignored (client, attr);
                ippDeleteAttribute (client->request, attr);
            }
        }
    }

    if ((attr = ippFindAttribute (client->request, "job-hold-until", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1
            || (ippGetValueTag (attr) != IPP_TAG_NAME && ippGetValueTag (attr) != IPP_TAG_NAMELANG
                && ippGetValueTag (attr) != IPP_TAG_KEYWORD)
            || strcmp (ippGetString (attr, 0, NULL), "no-hold")) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "job-impressions", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_INTEGER || ippGetInteger (attr, 0) < 0) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "job-name", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1
            || (ippGetValueTag (attr) != IPP_TAG_NAME && ippGetValueTag (attr) != IPP_TAG_NAMELANG)) {
            respond_unsupported (client, attr);
            valid = 0;
        }

        ippSetGroupTag (client->request, &attr, IPP_TAG_JOB);
    } else {
        ippAddString (client->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, "Untitled");
    }

    if ((attr = ippFindAttribute (client->request, "job-priority", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_INTEGER || ippGetInteger (attr, 0) < 1
            || ippGetInteger (attr, 0) > 100) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "job-sheets", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1
            || (ippGetValueTag (attr) != IPP_TAG_NAME && ippGetValueTag (attr) != IPP_TAG_NAMELANG
                && ippGetValueTag (attr) != IPP_TAG_KEYWORD)
            || strcmp (ippGetString (attr, 0, NULL), "none")) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "media", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1
            || (ippGetValueTag (attr) != IPP_TAG_NAME && ippGetValueTag (attr) != IPP_TAG_NAMELANG
                && ippGetValueTag (attr) != IPP_TAG_KEYWORD)) {
            respond_unsupported (client, attr);
            valid = 0;
        } else {
            supported = ippFindAttribute (client->printer->attrs, "media-supported", IPP_TAG_KEYWORD);

            if (!ippContainsString (supported, ippGetString (attr, 0, NULL))) {
                if (fidelity) {
                    respond_unsupported (client, attr);
                    valid = 0;
                } else {
                    respond_ignored (client, attr);
                    ippDeleteAttribute (client->request, attr);
                }
            }
        }
    }

    if ((attr = ippFindAttribute (client->request, "media-col", IPP_TAG_ZERO)) != NULL) {
        ipp_t *col,              /* media-col collection */
            *size;               /* media-size collection */
        ipp_attribute_t *member, /* Member attribute */
            *x_dim,              /* x-dimension */
            *y_dim;              /* y-dimension */
        int x_value,             /* y-dimension value */
            y_value;             /* x-dimension value */

        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_BEGIN_COLLECTION) {
            respond_unsupported (client, attr);
            valid = 0;
        }

        col = ippGetCollection (attr, 0);

        if ((member = ippFindAttribute (col, "media-size-name", IPP_TAG_ZERO)) != NULL) {
            if (ippGetCount (member) != 1
                || (ippGetValueTag (member) != IPP_TAG_NAME && ippGetValueTag (member) != IPP_TAG_NAMELANG
                    && ippGetValueTag (member) != IPP_TAG_KEYWORD)) {
                respond_unsupported (client, attr);
                valid = 0;
            } else {
                supported = ippFindAttribute (client->printer->attrs, "media-supported", IPP_TAG_KEYWORD);

                if (!ippContainsString (supported, ippGetString (member, 0, NULL))) {
                    if (fidelity) {
                        respond_unsupported (client, attr);
                        valid = 0;
                    } else {
                        respond_ignored (client, attr);
                        ippDeleteAttribute (client->request, attr);
                    }
                }
            }
        } else if ((member = ippFindAttribute (col, "media-size", IPP_TAG_BEGIN_COLLECTION)) != NULL) {
            if (ippGetCount (member) != 1) {
                respond_unsupported (client, attr);
                valid = 0;
            } else {
                size = ippGetCollection (member, 0);

                if ((x_dim = ippFindAttribute (size, "x-dimension", IPP_TAG_INTEGER)) == NULL
                    || ippGetCount (x_dim) != 1
                    || (y_dim = ippFindAttribute (size, "y-dimension", IPP_TAG_INTEGER)) == NULL
                    || ippGetCount (y_dim) != 1) {
                    respond_unsupported (client, attr);
                    valid = 0;
                } else {
                    x_value = ippGetInteger (x_dim, 0);
                    y_value = ippGetInteger (y_dim, 0);
                    supported
                        = ippFindAttribute (client->printer->attrs, "media-size-supported", IPP_TAG_BEGIN_COLLECTION);
                    count = ippGetCount (supported);

                    for (i = 0; i < count; i++) {
                        int x_min, x_max; // Min/max width
                        int y_min, y_max; // Min/max length

                        size = ippGetCollection (supported, i);
                        x_dim = ippFindAttribute (size, "x-dimension", IPP_TAG_ZERO);
                        y_dim = ippFindAttribute (size, "y-dimension", IPP_TAG_ZERO);

                        if (ippGetValueTag (x_dim) == IPP_TAG_INTEGER) {
                            x_min = ippGetInteger (x_dim, 0) - 100;
                            x_max = ippGetInteger (x_dim, 0) + 100;
                        } else {
                            x_min = ippGetRange (x_dim, 0, &x_max);
                        }

                        if (ippGetValueTag (y_dim) == IPP_TAG_INTEGER) {
                            y_min = ippGetInteger (y_dim, 0) - 100;
                            y_max = ippGetInteger (y_dim, 0) + 100;
                        } else {
                            y_min = ippGetRange (y_dim, 0, &y_max);
                        }

                        if ((x_value < x_min || x_value > x_max) && (y_value < y_min || y_value > y_max))
                            break;
                    }

                    if (i >= count) {
                        if (fidelity) {
                            respond_unsupported (client, attr);
                            valid = 0;
                        } else {
                            respond_ignored (client, attr);
                            ippDeleteAttribute (client->request, attr);
                        }
                    }
                }
            }
        }
    }

    if ((attr = ippFindAttribute (client->request, "multiple-document-handling", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_KEYWORD
            || (strcmp (ippGetString (attr, 0, NULL), "separate-documents-uncollated-copies")
                && strcmp (ippGetString (attr, 0, NULL), "separate-documents-collated-copies"))) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "orientation-requested", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_ENUM
            || ippGetInteger (attr, 0) < IPP_ORIENT_PORTRAIT || ippGetInteger (attr, 0) > IPP_ORIENT_REVERSE_PORTRAIT) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "page-ranges", IPP_TAG_ZERO)) != NULL) {
        if (ippGetValueTag (attr) != IPP_TAG_RANGE) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "print-quality", IPP_TAG_ZERO)) != NULL) {
        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_ENUM
            || ippGetInteger (attr, 0) < IPP_QUALITY_DRAFT || ippGetInteger (attr, 0) > IPP_QUALITY_HIGH) {
            respond_unsupported (client, attr);
            valid = 0;
        }
    }

    if ((attr = ippFindAttribute (client->request, "printer-resolution", IPP_TAG_ZERO)) != NULL) {
        supported = ippFindAttribute (client->printer->attrs, "printer-resolution-supported", IPP_TAG_RESOLUTION);

        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_RESOLUTION || !supported) {
            respond_unsupported (client, attr);
            valid = 0;
        } else {
            int xdpi,        /* Horizontal resolution for job template attribute */
                ydpi,        /* Vertical resolution for job template attribute */
                sydpi;       /* Vertical resolution for supported value */
            ipp_res_t units, /* Units for job template attribute */
                sunits;      /* Units for supported value */

            xdpi = ippGetResolution (attr, 0, &ydpi, &units);
            count = ippGetCount (supported);

            for (i = 0; i < count; i++) {
                if (xdpi == ippGetResolution (supported, i, &sydpi, &sunits) && ydpi == sydpi && units == sunits)
                    break;
            }

            if (i >= count) {
                if (fidelity) {
                    respond_unsupported (client, attr);
                    valid = 0;
                } else {
                    respond_ignored (client, attr);
                    ippDeleteAttribute (client->request, attr);
                }
            }
        }
    }

    if ((attr = ippFindAttribute (client->request, "sides", IPP_TAG_ZERO)) != NULL) {
        const char *sides = ippGetString (attr, 0, NULL);
        /* "sides" value... */

        if (ippGetCount (attr) != 1 || ippGetValueTag (attr) != IPP_TAG_KEYWORD) {
            respond_unsupported (client, attr);
            valid = 0;
        } else if ((supported = ippFindAttribute (client->printer->attrs, "sides-supported", IPP_TAG_KEYWORD))
                   != NULL) {
            if (!ippContainsString (supported, sides)) {
                if (fidelity) {
                    respond_unsupported (client, attr);
                    valid = 0;
                } else {
                    respond_ignored (client, attr);
                    ippDeleteAttribute (client->request, attr);
                }
            }
        } else if (strcmp (sides, "one-sided")) {
            if (fidelity) {
                respond_unsupported (client, attr);
                valid = 0;
            } else {
                respond_ignored (client, attr);
                ippDeleteAttribute (client->request, attr);
            }
        }
    }

    return (valid);
}
